Deploy a Docker container from a Dockerfile
Lizard hosts Docker containers that it builds from your Dockerfile. Put the file at the root of your project and run lizard up, or connect a GitHub repository: Lizard builds the image on its own build machines, so you need no local Docker. It takes the port from EXPOSE or --port, passes it to the container in PORT, and serves the container over HTTPS on an onlizard.com domain. Lizard does not pull ready-made images from a registry; to run a public image, write a one-line Dockerfile that starts FROM it.
Test status: this page describes Lizard’s builder, deployer and CLI 4.0.42 as of 2026-10-12, checked against their source. We did not run a new deploy for it. The Umami guide deploys a one-line FROM Dockerfile in this way and records its test.
Before you start
- A
Dockerfilewhose app listens on0.0.0.0. A server bound to127.0.0.1accepts no connections from outside the container. - Lizard CLI, signed in with
lizard login, on Pro or the 7-day Pro trial (card required). See pricing.
If you have no Dockerfile, try a deploy without one first: lizardpack detects Go, Node.js, Python, Ruby, PHP, Java, Rust and static sites. See the build pipeline.
An example Dockerfile
For a Node.js server such as the one in Deploy a Node.js app:
FROM node:22-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "server.js"]Add a .dockerignore so local files stay out of the image:
node_modules
.git
.env*Deploy with lizard up
lizard init --name my-container
lizard add --service web
lizard up --service weblizard up reads the first EXPOSE line of the local Dockerfile, prints Detected port 3000 from Dockerfile and sets that as the service port. Pass --port <number> to choose another. An upload always uses the root Dockerfile as written. See deploy from local code for what the upload includes.
Deploy from GitHub
lizard add -r your-org/your-app -n webEvery push to the tracked branch rebuilds the image. Lizard uses the root Dockerfile unless it only copies output built elsewhere (COPY dist … with no npm, pip, go build or similar RUN step); then lizardpack writes a replacement. To always use yours, set dockerfilePath:
lizard service set web --set dockerfilePath=DockerfileSee Dockerfile changes don’t apply and deploy from GitHub.
A Dockerfile in a subdirectory or with another name
dockerfilePath is relative to the build directory, which rootDirectory sets. For a monorepo service:
lizard service set api --set rootDirectory=apps/api --set dockerfilePath=Dockerfile.prodLeave buildCommand and startCommand empty on a Dockerfile service. Without dockerfilePath, either one makes Lizard write its own Dockerfile from the commands instead of using yours. With dockerfilePath set, a startCommand still replaces the image’s ENTRYPOINT and CMD: Lizard runs sh -c "<start command>".
Which port Lizard routes to
Lizard picks the service port in this order:
- A port you set:
lizard up --port,lizard port, the dashboard, or theEXPOSEline thatlizard upread. 0, which is worker mode: no port, no route.- A
PORTvariable you set on the service. - The first port the built image exposes, including ports from its base image. Ports sort as text, so
3000comes before80. 3000.
Inside the container, PORT holds the chosen port. Read it, or listen on the same fixed port. To change the port later, run lizard port 8080 --service web, then lizard redeploy --service web. See networking.
What reaches the build
- Build arguments: only
COMMIT_SHA. DeclareARG COMMIT_SHAto use it. OtherARGlines keep their default or stay empty. - Variables: service variables and secrets do not reach a Dockerfile build, with one exception for
VITE_*andNEXT_PUBLIC_*values described in build-time variables. Write public values into the Dockerfile or commit them; keep secrets for run time. - Base images: the builder has no login for your registries, so every
FROMimage must be public. - Changes Lizard makes: before building, Lizard adds
ARG COMMIT_SHAahead of your sourceCOPYand adds BuildKit cache mounts tonpm,yarn,pnpmandbuninstall steps. Cache mounts you write yourself keep working, scoped to this service; a--mountoption written with quotes fails the build.
Watch the build with lizard logs --build --service web.
At run time
- Health check: Lizard ignores
HEALTHCHECKand checks that the container accepts TCP connections on the service port. See service never healthy. - User: the image’s
USERapplies. A non-root user may lack permission to open a port below 1024, so prefer8080over80for such images. - Variables: the container gets your service variables,
PORTand theLIZARD_*platform variables. See variables and secrets. - Files: anything the container writes is lost on the next deploy. Use Managed Postgres, Managed Object Storage or another service for data; see storage and recovery.
Run a public image
Lizard builds every service from source, so wrap a public image in a Dockerfile:
FROM ghcr.io/umami-software/umami:3.3.1
EXPOSE 3000Use a version tag rather than latest to select the intended release. Run Umami and Run Flowise use this pattern with Managed Postgres.
Common problems
| Symptom | Fix |
|---|---|
| Your Dockerfile edits do not show up | On GitHub, Lizard may treat the file as incomplete; set dockerfilePath. Also check that the service has no buildCommand or startCommand. |
| Deploy never becomes healthy | The app listens on another port or on 127.0.0.1. Compare lizard port --service web with the log. |
pull access denied for the base image | The image is private or the name is wrong. Use a public image. |
An ARG value is empty | Lizard passes no build arguments except COMMIT_SHA. Set a default in the Dockerfile. |
| The service runs but serves nothing | It may be in worker mode: lizard port --service web prints worker mode. |
Related
Updated