Back to Blog
dockerdevopsfrontenddeployment

Docker for Frontend Developers

A practical introduction to Docker from a frontend perspective — containerizing Next.js apps, managing development environments, and CI/CD.

Aditya Vikram Mahendru3 min read
Docker for Frontend Developers

Why Frontend Developers Need Docker

Docker isn't just for backend engineers. As frontend applications grow in complexity — with environment variables, API proxies, SSR, and serverless functions — Docker provides consistent environments from development through production.

The Problem

How many times have you heard "it works on my machine"? Different Node versions, operating systems, and system dependencies cause subtle bugs that waste hours.

Docker solves this by packaging your entire environment into a container.

Your First Dockerfile

FROM node:20-alpine AS base

# Install dependencies only when needed
FROM base AS deps
WORKDIR /app
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile

# Build the application
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN bun run build

# Production image
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static

EXPOSE 3000
CMD ["node", "server.js"]

Multi-Stage Builds

The Dockerfile above uses multi-stage builds — a pattern that keeps the final image small by discarding build tools and intermediate artifacts:

StagePurposeSize
baseBase Node image with system deps~150MB
depsInstall dependenciesAdds ~200MB (discarded later)
builderCompile the appAdds ~50MB (discarded later)
runnerProduction image — only what's needed~180MB final

Docker Compose for Development

# docker-compose.yml
services:
  app:
    build:
      context: .
      target: deps
    ports:
      - "3000:3000"
    volumes:
      - .:/app
      - /app/node_modules
    environment:
      - NODE_ENV=development
      - CONVEX_URL=${CONVEX_URL}
    command: bun run dev

  db:
    image: postgres:16-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: myapp
      POSTGRES_PASSWORD: secret

volumes:
  postgres_data:

Essential Docker Commands

# Build the image
docker build -t my-app .

# Run the container
docker run -p 3000:3000 my-app

# Development with compose
docker compose up

# List running containers
docker ps

# View logs
docker logs -f my-app

# Clean up
docker system prune -a

Optimizing for Next.js

Standalone Output

Next.js can produce a standalone output that includes only the files needed for production:

// next.config.js
module.exports = {
  output: "standalone",
}

This reduces the deployment package significantly and works perfectly with the multi-stage Dockerfile pattern above.

Environment Variables

# Build-time args
ARG NEXT_PUBLIC_SITE_URL
ENV NEXT_PUBLIC_SITE_URL=$NEXT_PUBLIC_SITE_URL

# Runtime env vars (can be overridden at deployment)
ENV DATABASE_URL=""
ENV AUTH_SECRET=""

CI/CD Integration

# .github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Build Docker image
        run: docker build -t my-app .
      
      - name: Deploy to server
        run: |
          docker save my-app | ssh user@server docker load
          ssh user@server "docker compose up -d"

Best Practices

  1. Use .dockerignore — exclude node_modules, .next, .git
  2. Pin base image versions — avoid :latest, use :20-alpine
  3. Layer caching — order instructions from least to most frequently changing
  4. Health checks — add HEALTHCHECK to detect failing containers
  5. Non-root user — create and use a non-root user for security

Conclusion

Docker eliminates environment inconsistencies and streamlines deployment. Start with a simple Dockerfile for your Next.js app, add Docker Compose for development, and expand from there. The upfront investment pays for itself in the first debugging session saved.