๐ŸŒ Detecting your locationโ€ฆ

How to Deploy a Docker Container to Production in 2026: A Practical Guide

โฑ๏ธ5 min read  ยท  989 words

Getting a Docker container running locally is easy. Getting it running safely in production โ€” with proper configuration, health checks, and zero-downtime updates โ€” takes a few more steps. This guide walks through deploying a containerized application to production the right way in 2026, from a production-ready image to a reverse proxy and updates that do not drop traffic.

Step 1: Write a Production-Ready Dockerfile

Start with a multi-stage build so your final image contains only what it needs to run โ€” not build tools and dev dependencies. Smaller images pull faster, start faster, and have a smaller attack surface.

# Build stage
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Runtime stage
FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]

Note the USER node line โ€” never run production containers as root. Running as a non-privileged user limits damage if the app is compromised.

Step 2: Handle Configuration with Environment Variables

Never bake secrets into your image. Pass configuration and secrets at runtime through environment variables or a secrets manager. Your image should be identical across staging and production; only the environment differs.

docker run -d \
  --name myapp \
  --env-file /etc/myapp/production.env \
  --restart unless-stopped \
  -p 3000:3000 \
  myapp:1.4.0

Keep the .env file off the image and out of version control. On a single host, an env file with locked-down permissions works; at scale, use Docker secrets, AWS Secrets Manager, or your platform’s secret store.

Step 3: Add a Health Check

A health check lets Docker (and your orchestrator or reverse proxy) know whether the container is actually ready to serve traffic, not just running. Add an endpoint like /health to your app, then declare the check.

HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
  CMD wget -qO- http://localhost:3000/health || exit 1

With this in place, docker ps shows a healthy or unhealthy status, and orchestrators can avoid routing traffic to containers that are still warming up or have gone bad.

Step 4: Use Docker Compose for a Clean Setup

Even on a single server, Compose makes your deployment reproducible and self-documenting. It captures the image, ports, environment, restart policy, and dependencies in one file.

services:
  app:
    image: myapp:1.4.0
    restart: unless-stopped
    env_file: /etc/myapp/production.env
    expose:
      - "3000"
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
      interval: 30s
      timeout: 3s
      retries: 3
    depends_on:
      - db
  db:
    image: postgres:17-alpine
    restart: unless-stopped
    env_file: /etc/myapp/db.env
    volumes:
      - pgdata:/var/lib/postgresql/data
volumes:
  pgdata:

Note the named volume for Postgres โ€” container storage is ephemeral, so any data you must keep belongs in a volume, never inside the container’s writable layer.

Step 5: Put a Reverse Proxy in Front

Do not expose your app’s port directly to the internet. Put a reverse proxy (Nginx, Caddy, or Traefik) in front to terminate TLS, add security headers, and route traffic. Caddy is popular because it obtains and renews HTTPS certificates automatically.

# Caddyfile
myapp.example.com {
    reverse_proxy app:3000
    encode gzip
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains"
        X-Content-Type-Options nosniff
    }
}

The proxy handles HTTPS and headers while your app focuses on serving requests over plain HTTP inside the private Docker network.

Step 6: Deploy Updates Without Downtime

The naive update โ€” stop the old container, start the new one โ€” drops traffic during the gap. For zero-downtime updates, start the new container, wait for it to pass health checks, then switch the proxy and remove the old one. Traefik and orchestrators automate this; on a simple setup you can run two versions briefly and cut over.

# Pull the new version and recreate with Compose
docker compose pull app
docker compose up -d --no-deps app
# Compose starts the new container; verify health before removing old

Always tag images with a specific version (myapp:1.4.0), never rely on latest in production. Explicit tags make rollbacks trivial โ€” you know exactly which version to redeploy.

Step 7: Logging and Monitoring

Configure a log driver so logs go somewhere durable, and cap log size so a container cannot fill the disk. Ship logs to a central place (Loki, CloudWatch, or an ELK stack) for anything beyond a hobby project.

docker run -d \
  --log-driver json-file \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  myapp:1.4.0

Frequently Asked Questions

Q: Should I run my database in a container in production?
A: For small projects, yes โ€” with a named volume for persistence and regular backups. For anything critical or high-scale, a managed database (RDS, Cloud SQL) reduces operational risk considerably.

Q: Why not just expose the app port directly?
A: You would lose TLS termination, security headers, and a single point to manage routing. A reverse proxy also lets you run multiple apps on one host and handle certificates centrally.

Q: How do I keep secrets out of the image?
A: Pass them at runtime via environment variables, an env file with locked permissions, or a secrets manager. Never COPY a secrets file into the image or hardcode credentials in the Dockerfile.

Q: What restart policy should I use?
A: unless-stopped for most services โ€” it restarts the container on failure and after a host reboot, but respects a manual stop.

Q: How do I roll back a bad deploy?
A: Because you tagged images by version, redeploy the previous tag (docker compose up -d with the old version). This is why latest is dangerous in production โ€” it makes rollbacks ambiguous.

Conclusion

Deploying Docker to production safely comes down to a handful of disciplines: build a lean multi-stage image running as a non-root user, inject config and secrets at runtime, add health checks, front the app with a reverse proxy for TLS, tag images by version for clean rollbacks, and cap your logs. Capture it all in a Compose file so the setup is reproducible. Get these fundamentals right and your containerized app will run reliably, update without dropping traffic, and be easy to roll back when something goes wrong.

MD Rafikul Islam

Written by

MD Rafikul Islam is a software developer and the editor of TechPulse. He writes about developer tooling, hardware, and the practical decisions that come up in day-to-day engineering work โ€” which laptop to buy, which framework to commit to, why a build broke at 2am. He tests the tools he writes about and says plainly when something is not worth the money. Corrections and corrections requests are welcome at rony.yf25@gmail.com.

โœ๏ธ Leave a Comment

Your email address will not be published. Required fields are marked *

๐ŸŒ Read in:๐Ÿ‡ฌ๐Ÿ‡ง English๐Ÿ‡ฉ๐Ÿ‡ช Deutsch๐Ÿ‡ง๐Ÿ‡ท Portuguรชs๐Ÿ‡ธ๐Ÿ‡ฆ ุงู„ุนุฑุจูŠุฉ๐Ÿ‡ฎ๐Ÿ‡ณ เคนเคฟเคจเฅเคฆเฅ€๐Ÿ‡ง๐Ÿ‡ฉ เฆฌเฆพเฆ‚เฆฒเฆพ