You're helping create a Dockerfile for an application that will be deployed on
Unkey Deploy (https://unkey.com/docs/build-and-deploy/overview). Before writing
anything, inspect the repository so the Dockerfile matches how the app is
actually built and run.
## Rule: never assume, always ask
If anything is ambiguous, stop and ask the user. Do not guess. This includes
(but is not limited to):
- Which app in a monorepo should be deployed.
- Which Node/Python/Go/Bun/etc. version to target if the repo doesn't pin one.
- Which package manager to use if multiple lockfiles exist.
- Which command starts the app, if there are several plausible scripts.
- Which port the app listens on, if it isn't clearly set from `PORT`.
- Whether the build needs secrets, and which ones.
- Whether the app writes to disk at runtime, and where.
Ask one question at a time. Wait for the answer, then ask the next one.
Batching several questions into a single message makes it hard for the user
to answer each in detail, and they often miss or skip some. A wrong
assumption here means the build succeeds but the deploy fails, and the user
has to debug a running container to figure out why.
## Step 1: Inspect the repo
Identify:
- Language and runtime version (Node.js, Bun, Python, Go, Rust, Java, etc.).
- Package manager and lockfile (npm, pnpm, yarn, poetry, uv, pip, cargo, go mod).
- Build tool and output directory (next, vite, tsc, esbuild, turbo, go build,
cargo build, etc.).
- The start command and the port the app listens on. Check for `process.env.PORT`,
`os.getenv("PORT")`, config files, or framework defaults.
- Monorepo tooling (pnpm/npm/yarn workspaces, Turborepo, Nx, Lerna, Cargo
workspaces, Go workspaces). If present, ask which app we're dockerizing and
figure out which internal packages it depends on.
- Any existing Dockerfile or container config to preserve intent from.
## Step 2: Respect Unkey Deploy's runtime constraints
These are not general Docker guidance. The image will not run correctly without
them.
1. Listen on the `PORT` environment variable (default 8080). Unkey injects
`PORT` at startup. Do not hardcode a port.
2. Handle `SIGTERM` for graceful shutdown. Use the exec form for `CMD`
(for example `CMD ["node", "dist/index.js"]`) so the app process is PID 1
and receives signals directly instead of a shell swallowing them.
3. Scratch and ephemeral storage:
- `/tmp`: small, memory-backed scratch space, always available.
- `/data`: available only if ephemeral storage is configured. The mount
path is exposed in the `UNKEY_EPHEMERAL_DISK_PATH` env var.
4. Build-time secrets come from a mounted file, never `ARG` or `ENV`. If the
build needs secrets (private package tokens, codegen against a real DB
URL), consume them like this:
RUN --mount=type=secret,id=env,target=/run/secrets/.env \
set -a && . /run/secrets/.env && set +a && \
<your build command>
`ARG` and `ENV` values are visible in `docker history` and leak into the
final image. Don't use them for secrets.
## Step 3: Apply these best practices
- Multi-stage build: one stage for dependencies and build, a minimal final
stage with only the runtime artifacts. Keeps the image small and keeps
build tools out of production.
- Pin the base image to a specific version (for example `node:22-alpine`,
not `node:latest`).
- Cache dependency installs. Copy only the manifest files (`package.json`,
lockfile, `go.mod`, `Cargo.toml`, etc.) first, install, then copy the rest
of the source. Reordering kills the cache.
- Prefer slim or distroless base images when the runtime is compatible.
- Run as a non-root user (`USER node`, `USER nobody`, or a dedicated user
you create). Non-root is cheap extra defense.
- One foreground process. No supervisord, no `&`, no wrapper shell scripts
that background the app.
## Step 4: Monorepo handling
If the project is a monorepo:
- Do not assume any folder convention. Monorepos put apps under `apps/`,
`packages/`, `services/`, `cmd/`, or anywhere else. Read the repo's own
workspace config (pnpm `workspace:`, npm/yarn `workspaces`, `turbo.json`,
`nx.json`, `go.work`, `Cargo.toml` workspace members) to find the real
paths. If more than one candidate fits what the user described, ask which
one to deploy.
- Ship only the target app and its workspace dependencies. Use the
monorepo's pruning tool: `pnpm deploy`, `turbo prune`, Nx's `build
--prod`, or equivalent. Do not COPY the whole monorepo into the image.
- Place the Dockerfile next to the app you're deploying (wherever that
actually lives in the repo), and keep the build context at the repo root
so workspace packages resolve correctly.
- In the summary, tell the user the exact **Root directory** and
**Dockerfile** values to set in Unkey's app settings, using the real paths
you used.
## Step 5: Write a .dockerignore
Create a `.dockerignore` next to the Dockerfile's build context. Always exclude:
.git
.gitignore
node_modules
.next
dist
build
out
coverage
.env
.env.*
*.log
.DS_Store
.vscode
.idea
README.md
Dockerfile
.dockerignore
Add language- and framework-specific entries based on what you found (for
example `target/` for Rust, `__pycache__/` and `.venv/` for Python, `vendor/`
for Go if `go mod vendor` isn't used, `.turbo/` for Turborepo). A lean build
context makes builds faster and prevents `.env` files or other local state
from accidentally shipping in the image.
## Output
Produce:
1. A `Dockerfile` at a path that matches the repo's real layout. Do not
invent a folder structure. Place it next to the app you're deploying.
2. A matching `.dockerignore` at the correct build-context root.
3. A short note listing the exact values to configure in Unkey's app
settings, using the real paths you used:
- Root directory
- Dockerfile path
- Port (if the app doesn't listen on 8080)
- Start command (only if the Dockerfile's `CMD` should be overridden at
the platform level; usually leave this empty)