Build Pipeline
Lizard builds a container image from your source on its build nodes, then runs the image in an isolated pod. You do not need Docker locally. The rules below determine how Lizard builds the image. For a comparison of hosts that build images and hosts that require an image, see container as a service.
Build decision order
The platform picks exactly one build strategy, in this order:
- Repo Dockerfile at
dockerfilePath: ifdockerfilePathis set on the service, that Dockerfile is used unchanged, even when build or start commands are also set. A setstartCommandstill replaces the Dockerfile’sENTRYPOINTandCMDat run time. See Deploy with a Dockerfile. - Synthesized Dockerfile: otherwise, if
buildCommandand/orstartCommandare set on the service (or passed vializard up --build-command/--start-command), Lizard generates a Dockerfile from those commands. lizardpack is not invoked. - Dockerfile in the source: otherwise a
Dockerfileat the root of the build directory is used (see the GitHub exception below). - lizardpack auto-detect: otherwise the platform runs lizardpack, its buildpack/Dockerfile generator.
lizardpack auto-detect
lizardpack inspects your repo and builds a multi-stage image. Supported stacks, matched in this order:
Hugo → Go → Node → Python → Rust → Ruby → PHP → Java → static
See Framework guides for production scripts, adapters, output paths, and ports: Hugo, Go, Node (Node.js, Next.js, Nuxt, SvelteKit, Astro, React, Vue, Docusaurus, VitePress), Python (Django, FastAPI, Streamlit) and Ruby (Rails). Detection uses project files and dependencies; a repository that matches several providers follows this order. To pin a Node.js or Python version, see runtime versions.
On this path:
- Uploaded source (
lizard up) always uses a rootDockerfileas written. - GitHub source uses a root
Dockerfileas written unless it looks incomplete: it has aCOPYofbuild,dist,out,.nextorpublicwritten without a trailing slash (COPY dist /usr/share/nginx/html) and noRUNline that starts withnpm,yarn,pnpm,bun,pip,pip3,bundle,cargo,go build,mvnorgradle. lizardpack then generates a replacement. - With no Dockerfile, lizardpack generates one for you.
- The start command is auto-detected: a
Procfileweb:line (Python/Ruby) orpackage.jsonscripts.start(Node) is picked up automatically. For Django, Flask and FastAPI start commands, see Python app hosting. - The port is inferred from
EXPOSE, framework defaults, or an explicitPORTenv.
Gotcha: On GitHub source, a Dockerfile that copies pre-built output (
COPY dist /dest) and has no package-managerRUNstep is treated as incomplete and regenerated by lizardpack. Add a real build step, or setdockerfilePathto force verbatim use. See Dockerfile changes don’t apply.
What triggers a rebuild
| Action | Rebuilds? |
|---|---|
git push to the tracked branch | ✅ via GitHub webhook |
lizard redeploy / lizard up | ✅ explicit |
Changing VITE_* or NEXT_PUBLIC_* vars | ✅ rebuilds a running service; see build-time variables for when the values reach the build |
service set of build fields (repoUrl, branch, sourceType, buildCommand, dockerfilePath, rootDirectory) | ✅ auto-rebuilds a running service; otherwise run lizard redeploy |
service set of runtime-only fields (startCommand, preDeployCommand, containerPort, watchPatterns) | ❌ run lizard redeploy to apply |
| Any other env var / secret change | ❌ applied on a quick restart (no rebuild) |
Don’t double-build. After a
service setthat changes a build field on a running service, Lizard starts a rebuild. Runninglizard redeployafterward cancels that build and starts it again (why). A crashed or never-deployed service does not rebuild on its own; runlizard redeploy.
Build-time variables
Lizard writes VITE_* and NEXT_PUBLIC_* variables into the Dockerfile as ENV lines in two cases:
- GitHub services without
dockerfilePath: the lizardpack Dockerfile or the repo’s rootDockerfilegets them. - Services with
buildCommandorstartCommandwhose synthesized Dockerfile is a Node build.
Services deployed with lizard up and no build or start command get no Lizard variables at build time. No service gets build args other than COMMIT_SHA, so an ARG in your Dockerfile stays empty. For public values with lizard up, commit them in .env.production (check that .gitignore does not exclude it), write them into your Dockerfile, or, for a Node server app, set both buildCommand and startCommand.
Watching a build
Build logs stream during lizard up. For any service:
lizard logs --build # the most recent build's logs
lizard events # deploy history + replica statusIf a build fails, read lizard logs --build, fix the cause (in your repo, or by adjusting buildCommand / startCommand via lizard service set), then lizard redeploy.
Runtime notes
- No Docker
HEALTHCHECK. The runtime doesn’t run Docker’s healthcheck loop, soHEALTHCHECKdirectives are ignored. Lizard runs a TCP probe against your port instead (skipped in worker mode). - Don’t write a Dockerfile unsolicited. lizardpack auto-detects most stacks: try a deploy first, and only add a Dockerfile (or set
dockerfilePath) if the auto-build doesn’t fit.
Build configuration reference
| Field | Description |
|---|---|
buildCommand | Command to build your app → selects the synthesized Dockerfile path unless dockerfilePath is set |
startCommand | Command to start your app at runtime; replaces the image’s ENTRYPOINT and CMD |
preDeployCommand | Runs at the start of every container (each replica, each restart), before startCommand; only runs when startCommand is set. Make it safe to run many times and at the same time |
dockerfilePath | Path to a repo Dockerfile to use verbatim; wins over buildCommand / startCommand for the image build |
rootDirectory | Subdirectory to build from (monorepos) |
watchPatterns | Only redeploy when matching paths change |
containerPort | TCP port your app listens on (default 3000; 0 = worker) |
Set any of these with lizard service set <svc> --set <field>=<value>. Clear a field with an empty value, such as --set buildCommand=. See lizard service.
Updated