Core ConceptsBuild Pipeline

Build Pipeline

Builds run on Lizard’s build nodes — you don’t need Docker locally. When you deploy, the platform decides how to turn your source into a container image using a fixed decision order, then runs it in a Firecracker micro-VM.

Build decision order

The platform picks exactly one build strategy, in this order:

  1. Synthesized Dockerfile — if buildCommand and/or startCommand are set on the service (or passed via lizard up --build-command / --start-command), Lizard generates a Dockerfile from those commands. lizardpack is not invoked.
  2. Repo Dockerfile (verbatim) — if dockerfilePath is set on the service, that Dockerfile from your repo is used unchanged.
  3. lizardpack auto-detect — otherwise the platform clones your source and runs lizardpack, its buildpack/Dockerfile generator.

lizardpack auto-detect

lizardpack inspects your repo and builds an optimized multi-stage image. Supported stacks, matched in this order:

Go → Node → Python → Rust → Ruby → PHP → Java → static

On this path:

  • If a repo Dockerfile exists and has a real build step (a RUN <package-manager> line, not just COPY dist/), it’s used verbatim.
  • Otherwise lizardpack generates the Dockerfile for you.
  • The start command is auto-detected: a Procfile web: line (Python/Ruby) or package.json scripts.start (Node) is picked up automatically.
  • The port is inferred from EXPOSE, framework defaults, or an explicit PORT env.

Gotcha: A Dockerfile that only copies pre-built artifacts (COPY dist/, build/, out/, .next/, public/) without a RUN build step is treated as incomplete and gets regenerated by lizardpack. Add a real build step, or set dockerfilePath to force verbatim use.

What triggers a rebuild

ActionRebuilds?
git push to the tracked branch✅ via GitHub webhook
lizard redeploy / lizard up✅ explicit
Changing VITE_* or NEXT_PUBLIC_* vars✅ build-time values are baked in
service set of build fields (repoUrl, branch, sourceType, buildCommand, dockerfilePath, rootDirectory)✅ auto-rebuilds
service set of runtime-only fields (startCommand, preDeployCommand, containerPort, watchPatterns)❌ run lizard redeploy to apply
Any other env var / secret change❌ pushed live to the running VM (no rebuild)

Don’t double-build. After a service set that changes a build field, the rebuild fires automatically — don’t chain a lizard redeploy after it, or you’ll queue a second redundant build.

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 status

If 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. Firecracker VMs don’t run Docker’s healthcheck loop, so HEALTHCHECK directives are ignored. Lizard checks port reachability 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

FieldEffect
buildCommandCommand to build your app → forces the synthesized Dockerfile path
startCommandCommand to start your app at runtime
preDeployCommandRuns once before each deploy (e.g. DB migrations)
dockerfilePathPath to a repo Dockerfile to use verbatim
rootDirectorySubdirectory to build from (monorepos)
watchPatternsOnly redeploy when matching paths change
containerPortTCP port your app listens on (default 3000; 0 = worker)

Set any of these with lizard service set <svc> --set <field>=<value>. See the CLI Reference.