Skip to content
← All field notes

Note / 001 · Deployment

Deploying applications with Docker

A production-focused checklist for smaller images, safer containers, reliable health checks and predictable releases.

Published
2026-02-10
Reading time
7 min read

A container running locally proves that the application can start. It does not prove that the image is small, the process is secure, configuration is complete, or a deployment can recover safely.

A production-ready container should be repeatable, observable and disposable. You should be able to build the same artifact in CI, understand whether it is healthy, replace it without losing data, and roll back when a release fails.

Start with the image

Use a multi-stage build so compilers, source files and development dependencies do not automatically enter the runtime image.

FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

FROM node:22-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM node:22-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production

RUN addgroup --system --gid 1001 app && \
    adduser --system --uid 1001 --ingroup app app

COPY --from=build --chown=app:app /app/dist ./dist
COPY --from=build --chown=app:app /app/package.json ./package.json
COPY --from=build --chown=app:app /app/node_modules ./node_modules

USER app
EXPOSE 3000
CMD ["node", "dist/server.js"]

The exact output directory depends on the framework. The important pattern is that the runtime stage contains only what the process needs and runs as a non-root user.

Add a .dockerignore as well. Sending .git, local dependencies, test output and secrets into the build context makes builds slower and increases the chance of sensitive files entering an image layer.

Treat configuration as an API

Missing configuration should stop the process immediately with a useful message. Validate required variables at startup instead of discovering that DATABASE_URL is missing on the first request.

Do not bake environment-specific values or secrets into the image. Build one artifact and provide configuration at runtime through the deployment platform or a secret manager. Remember that ARG and ENV instructions can remain visible in image metadata and build history.

Make health checks tell the truth

A process being alive is different from an application being ready.

  • Liveness asks whether the process is stuck and should be restarted.
  • Readiness asks whether this instance can safely receive traffic.
  • Startup gives slow-starting applications time before liveness checks begin.

A readiness endpoint should verify only dependencies required to serve the request. If it checks every optional integration, a minor third-party outage can remove every healthy instance from service.

healthcheck:
  test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:3000/health/ready"]
  interval: 30s
  timeout: 3s
  retries: 3
  start_period: 20s

Keep state outside the container

Containers should be replaceable. Application logs should go to standard output, uploaded files should use durable object storage, and databases should use managed storage or explicitly backed-up volumes.

Writing important data only to the container filesystem creates a deployment that appears healthy until the container is recreated.

Handle shutdown correctly

During a rolling release, the platform sends a termination signal before replacing an instance. The application should stop accepting new work, finish in-flight requests within a deadline, close database connections and exit.

Use the exec form of CMD so the application receives signals directly. A shell-form command can leave the shell as PID 1 and interfere with signal forwarding.

A safer release checklist

Before promoting an image:

  1. Pin the base image deliberately and scan the final image for known vulnerabilities.
  2. Run the container as a non-root user with the smallest permissions it needs.
  3. Validate configuration during startup and keep secrets out of image layers.
  4. Exercise health endpoints and graceful shutdown in CI or staging.
  5. Tag the artifact with an immutable identifier such as the commit SHA.
  6. Apply database migrations as a controlled release step, not from every replica.
  7. Keep the previous image available and document the rollback command.

The useful mental model

Docker packages a process and its filesystem. Production readiness still comes from the surrounding system: configuration, orchestration, storage, monitoring and release discipline.

The container is ready when losing it is uneventful—not when it survives forever.

Further reading