DEV Community

Alex
Alex

Posted on

Docker in Production: What I Wish I Knew Before My First Deploy

My first Docker production deploy went like this:

  1. Built image locally
  2. Pushed to registry
  3. Pulled on server
  4. App crashed immediately
  5. Spent 6 hours debugging

Here's everything I learned the hard way.

Lesson 1: Multi-Stage Builds Are Non-Negotiable

# BAD: 1.2GB image with build tools in production
FROM node:18
COPY . .
RUN npm install
RUN npm run build
CMD ["node", "dist/server.js"]

# GOOD: 150MB image, only runtime deps
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:18-slim
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]
Enter fullscreen mode Exit fullscreen mode

Result: Image went from 1.2GB to 150MB. Deploys 8x faster.

Lesson 2: Health Checks Save Lives

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1
Enter fullscreen mode Exit fullscreen mode

Without this, Docker thinks your container is healthy even when the app inside is completely dead.

Lesson 3: Don't Run as Root

RUN addgroup --system app && adduser --system --ingroup app app
USER app
Enter fullscreen mode Exit fullscreen mode

One CVE in your dependency and an attacker has root access to your container.

Lesson 4: .dockerignore Matters

node_modules
.git
.env
*.md
tests/
coverage/
Enter fullscreen mode Exit fullscreen mode

I once accidentally included .env with production database credentials in a public Docker image.

Lesson 5: Log to stdout, Not Files

// BAD
fs.appendFileSync('/var/log/app.log', message);

// GOOD
console.log(JSON.stringify({
  timestamp: new Date().toISOString(),
  level: 'info',
  message: message
}));
Enter fullscreen mode Exit fullscreen mode

Docker captures stdout/stderr automatically.

Lesson 6: Pin Your Base Image Version

# BAD
FROM node:latest

# GOOD
FROM node:18.19.0-slim
Enter fullscreen mode Exit fullscreen mode

My Production Checklist

  • [ ] Multi-stage build
  • [ ] Non-root user
  • [ ] Health check defined
  • [ ] .dockerignore configured
  • [ ] Base image pinned
  • [ ] Logs go to stdout
  • [ ] Secrets via env vars, not baked in

Going Deeper

If you're moving to cloud infrastructure:

What was your worst Docker production moment?

Top comments (0)