# 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 that image in an isolated pod. Not every [container as a service](https://lizard.build/blog/container-as-a-service) builds the image for you; the blog compares the three shapes the category comes in.

## 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:

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

See [Framework guides](https://lizard.build/docs/framework-guides) for production scripts, adapters, output paths, and ports. Detection uses project files and dependencies; a repository that matches several providers follows this order.

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. For the exact line Django, Flask and FastAPI each want, see [Python app hosting](https://lizard.build/blog/python-app-hosting).
- 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

| Action | Rebuilds? |
|--------|-----------|
| `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 | ❌ applied on a quick restart (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:

```bash
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`.** The runtime doesn't run Docker's healthcheck loop, so `HEALTHCHECK` directives are ignored. Lizard runs a TCP probe against your port instead (skipped in [worker mode](https://lizard.build/docs/deploy/workers)).
- **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 → forces the **synthesized Dockerfile** path |
| `startCommand` | Command to start your app at runtime |
| `preDeployCommand` | Runs once before each deploy (e.g. DB migrations) |
| `dockerfilePath` | Path to a repo Dockerfile to use **verbatim** |
| `rootDirectory` | Subdirectory to build from (monorepos) |
| `watchPatterns` | Only redeploy when matching paths change |
| `containerPort` | TCP port your app listens on (default `3000`; `0` = [worker](https://lizard.build/docs/deploy/workers)) |

Set any of these with `lizard service set <svc> --set <field>=<value>`. See [`lizard service`](https://lizard.build/docs/cli/service).
