# Dockerfile

Most apps deploy with no configuration: Nimbus Land works out how to build them (see [Apps](apps.md)). When your language has no built-in support (Rust or .NET, for example) or you want full control over the image, put a `Dockerfile` at the root of your project. Nimbus Land builds it as written and runs the image like any other app.

## How it is chosen

A `Dockerfile` at the root of the app always wins over the automatic build for your language. A `Dockerfile` in a subdirectory (`docker/`, `examples/`) is ignored.

Nimbus Land still recognises the framework from your manifests, so framework defaults apply to the running app. A Rails app built from the `Dockerfile` that `rails new` writes still gets a generated `SECRET_KEY_BASE` and the storage volume suggestion (see [Ruby](ruby.md#rails)).

To force the Dockerfile build, pass `--builder dockerfile` to `nimbus deploy`, set it in `.nimbus.yml`:

```yaml
build:
  builder: dockerfile
```

or send `"builder": "dockerfile"` to the build API. To publish the repository as a static site instead, see [Static sites](static-sites.md).

## Checked before building

The deploy plan reads your `Dockerfile` before anything is built:

- **What runs** is the final stage's `ENTRYPOINT` and `CMD`. If the final stage declares an `EXPOSE` port other than the one your app receives traffic on, the plan notes it: `Your Dockerfile declares port 8080, but the app receives traffic on port 3000. Make it listen on the PORT variable.`
- **A `Procfile` is not used.** If you have one, the plan says `Your Procfile is not used when the Dockerfile builds the app.`
- **Every `COPY` and `ADD` source must exist** in the repository. A source that matches nothing (typically build output such as `dist/api` produced by a step outside the Dockerfile) would fail the build, so a first deploy is refused before anything is created: `Your Dockerfile copies dist/api, which is not in the repository. It is probably produced by a step this Dockerfile does not run.` The build API returns the code `copy_source_missing`. Globs are matched against your files, `COPY --from=…` is not checked, and `.dockerignore` is not consulted for this check. Once the app has served a healthy release, this becomes a note instead of a refusal.

## What your image needs

- **Listen on `PORT`.** Nimbus Land sets `PORT` and sends traffic only to it. An image that hard-codes another port builds and deploys, then never receives traffic. This is the most common Dockerfile problem. `ENV PORT=3000` in your `Dockerfile` is a fine default, because the platform's value overrides it.
- **Your `CMD` and `ENTRYPOINT` are used as-is.**
- **App variables are available at run time only.** They are not passed to the build as `--build-arg`s. Give non-secret build inputs `ARG` defaults in the `Dockerfile`, or move the step to run time. Never bake secrets into image layers.
- **There is no `.git` in the build.** Builds receive your source without its `.git` directory, so `RUN git rev-parse HEAD` fails with `not a git repository`. Pass a version through an `ARG` default or compute it at run time.
- **`/bin/sh` is needed for commands.** Release commands, one-off commands and scheduled tasks run through `/bin/sh`, so a `FROM scratch` or distroless image can't run them.

A `.dockerignore` at the project root is honoured.

## Example: Rust

`Cargo.toml`, `src/main.rs` and this `Dockerfile` are a complete app:

```dockerfile
FROM rust:1.83-slim AS build
WORKDIR /app
COPY . .
RUN cargo build --release --locked

FROM debian:bookworm-slim
COPY --from=build /app/target/release/my-app /usr/local/bin/my-app
ENV PORT=3000
CMD ["my-app"]
```

Your server must read `PORT`. Then deploy from the project directory:

```sh
nimbus deploy
```

`nimbus detect` reports `dockerfile` for this project. A project with a `Cargo.toml` and no `Dockerfile` gets a note that Rust needs one.

## Builds

Each app keeps a build cache: a `RUN` step whose inputs haven't changed since a recent build is reused instead of run again (the build log shows `Using caching version of cmd: …`). Order your `Dockerfile` so dependency installs come before copying the rest of your source, and code-only commits rebuild quickly. If the cache can't be used, the build simply runs from scratch.

Builds run in a sandboxed environment with a fixed memory limit, and pull your base images during the build.

When a build fails, its failure reason quotes the last line of the failing step's output that reports an error, or names the step (`… at step RUN mkdir /code/`) when there is no such line. The full log is on the build page, which refreshes on its own while the build runs.

## Troubleshooting

**The app never answers.** The build succeeds and the app starts, but its address times out or returns an error, and the release never becomes healthy. The image listens on a fixed port instead of `PORT`. Read `PORT` at start-up (most servers accept a `PORT` variable or a flag) and deploy again.

**The build ran out of memory.** The failure reads `The build ran out of memory (limit N GiB).` with a hint for your stack, typically during `next build`, a large bundle or a native compile. For Node, set `NODE_OPTIONS=--max-old-space-size=<MiB>` in the build step (the hint suggests a value); otherwise split or trim the step that needs the memory.

**A `RUN` step can't find a variable.** App variables aren't passed to the build. Use `ARG` defaults, or do the work at run time.

**`not a git repository`.** There's no `.git` in the build. See [What your image needs](#what-your-image-needs).
