Docker Production Deployment 2026: The Complete Problem-Solving Guide
By Huzi
File #530 · 4 min read
A Docker image that runs on your laptop tells you almost nothing about how it behaves in production. Production is where the container runs for months, eats memory until the host dies, writes logs until the disk fills, and hangs without exiting so nothing restarts it. Every one of those failures has a small fix. Here is the checklist.
The quick answer: Ship a small multi-stage image that runs as a non-root user, define a HEALTHCHECK plus a restart policy so hangs get restarted, cap memory and CPUs before the kernel does, and rotate logs before the disk fills. The goal is boring — and recovered-from.
Ship a small image that is not root
One image, two stages: build with the full toolchain, run with only the output. DevDependencies, compilers, and caches never reach production.
# syntax=docker/dockerfile:1.7
FROM node:22-slim AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22-slim
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
RUN chown -R node:node /app
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]
The USER node line matters as much as the two stages: the default Docker user is root, and a container escape as root is a very bad Tuesday. Add a strict .dockerignore — no node_modules, no .git, no .env — so you never ship your secrets as a cached layer.
A process that hangs is not running
Docker restarts a container only when its process exits. If the app deadlocks — alive, but serving nothing — nothing restarts it unless you define health:
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD node -e "fetch('http://localhost:3000/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
Pair that with a restart policy like restart: unless-stopped in Compose, and a container that dies at 4am comes back before your pager goes off.
Cap the resources before the kernel does
An unbounded container will happily eat the host's memory until the OOM killer fires, and it does not care that the database lives on the same box:
services:
app:
build: .
restart: unless-stopped
ports:
- "127.0.0.1:3000:3000"
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
reservations:
memory: 128M
Limits are a hard ceiling; reservations are a guaranteed floor on a noisy host.
Pro tip: Cap memory around one and a half times your observed peak, so one container crashes instead of the whole machine. Bind app ports to
127.0.0.1and let a reverse proxy face the world.
Logs will fill your disk unless you say otherwise
The default logging driver grows forever. In Compose, rotation is a few lines; on a plain host, the same settings live in /etc/docker/daemon.json:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
Ten megabytes per file, three files, rotated forever. Read them with docker logs -f app, and ship them somewhere searchable when you get serious.
Logs are memory you can search later; travel deserves the same paper trail. Keep every confirmation and receipt in one thread with HTG Travels — when a plan breaks mid-trip, there is a record to roll back to.
Before you call it production
Scan every image before it reaches the registry: docker scout cves myapp:latest, with the critical-only and exit-code flags wired into CI so a bad base image fails the build instead of your customers. None of this is advanced — it is the difference between "it runs" and "it recovers".
The checklist, at a glance:
| Failure | The fix |
|---|---|
| Container runs as root | Two-stage build, chown -R node:node /app, USER node |
| Hangs without exiting | HEALTHCHECK: 30s interval, 3s timeout, 3 retries |
| Nothing restarts it | restart: unless-stopped in Compose |
| Host memory eaten | Limits of 512M, reservations of 128M |
| Disk filled by logs | json-file driver, max-size 10m, max-file 3 |
| Vulnerable base image | docker scout cves wired into CI |
The bakers of Gaza fire their ovens back up every time the power returns — restarting a whole city's mornings by hand.
When the plan crashes — a cancelled flight, a missed connection — that is our restart policy. HTG Travels rebooks fast when airlines cancel, protects your hotel nights, and gets you moving again the same day from Sialkot, Lahore, or Islamabad. Recovery is the whole job. Everything starts with a text to +92 317 5054959 when the backlog clears.




