# Docker and containers

## Multi-stage example

```dockerfile
FROM denoland/deno:alpine AS build
WORKDIR /app
# Copy the manifest and lockfile and install dependencies first, so source changes don't invalidate the dependency layer
COPY deno.json deno.lock ./
RUN deno ci
COPY . .
RUN deno install --entrypoint main.ts

FROM denoland/deno:distroless
WORKDIR /app
COPY --from=build /app /app
# The dependency cache lives in DENO_DIR (/deno-dir), not /app — copy it explicitly
COPY --from=build /deno-dir /deno-dir
USER deno
EXPOSE 8000
CMD ["run", "--cached-only", "--allow-net", "--allow-env=PORT", "main.ts"]
```

<Callout type="warn" title="Multi-stage builds must copy DENO_DIR">
`deno install` caches into the global `DENO_DIR` (`/deno-dir` in the official images), not the project directory. Skipping this copy while keeping `--cached-only` makes the container fail at startup with a missing cache instead of downloading dependencies.
</Callout>

Choose `debian`, `alpine`, or `distroless` based on dependencies. If you need a shell, CAs, fonts, or system libraries, do not choose a less observable variant solely for image size.

## Production checks

- Pin a Deno image tag or digest; do not release from floating `latest`.
- Exclude `.git`, secrets, coverage, and local caches with `.dockerignore`.
- Run as non-root with a read-only root filesystem; mount only required write paths.
- Make `0.0.0.0` and `PORT` behavior explicit in the application.
- Let the platform send SIGTERM and measure graceful shutdown.
- If runtime dependency resolution has no network, cache during build and verify with `--cached-only`.

Official reference: [Deno and Docker](https://docs.deno.com/runtime/reference/docker/).
