Docker for Web Developers: Containerizing Full-Stack Applications | SoniNow Blog

Limited TimeLearn More

dockercontainersdevopsfull-stackdeployment

Docker for Web Developers: Containerizing Full-Stack Applications

Published

2026-06-23

Read Time

3 mins

Docker for Web Developers: Containerizing Full-Stack Applications

Docker eliminates the "it works on my machine" problem by packaging your application with its entire runtime environment. For web developers shipping full-stack applications — a Next.js frontend, a Node.js API, and a PostgreSQL database — Docker provides reproducible environments from development through production. Here is how to containerize effectively.

The Multi-Stage Build Pattern

Multi-stage builds keep your production images lean by separating build dependencies from runtime artifacts. A Next.js application benefits greatly from this approach.

# Stage 1: Dependencies
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production

# Stage 2: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

# Stage 3: Production runtime
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

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

The final image contains only the compiled output and production dependencies — no TypeScript sources, no devDependencies, no build tools. Image size drops from 1.2 GB to under 200 MB.

Docker Compose for Local Development

Docker Compose orchestrates multi-service environments. Define your frontend, API, database, and any supporting services in a single file.

# docker-compose.yml
version: '3.8'
services:
  frontend:
    build:
      context: ./frontend
      target: deps
    ports:
      - '3000:3000'
    volumes:
      - ./frontend:/app
      - /app/node_modules
    environment:
      - NEXT_PUBLIC_API_URL=http://localhost:4000
    depends_on:
      - api

  api:
    build: ./api
    ports:
      - '4000:4000'
    volumes:
      - ./api:/app
      - /app/node_modules
    environment:
      - DATABASE_URL=postgres://user:pass@db:5432/myapp
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16-alpine
    ports:
      - '5432:5432'
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: myapp
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U user']
      interval: 5s
      timeout: 5s
      retries: 5

volumes:
  pgdata:

The volume mounts enable hot-reloading — code changes on your host are reflected instantly inside the container without rebuilds.

Production Optimization

Production images need different treatment. Use .dockerignore to exclude development artifacts, set Node.js to production mode, and configure health checks.

node_modules
.git
.gitignore
*.md
.env.local
.env.development
test/
tests/
cypress/
.vscode/

Add health checks to your production Dockerfile:

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD node -e "require('http').get('http://localhost:3000/api/health', (r) => { process.exit(r.statusCode === 200 ? 0 : 1) })"

CI/CD Integration

Build and push images in your CI pipeline. A GitHub Actions example:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build Docker image
        run: |
          docker build \
            --target runner \
            --cache-from ${{ vars.REGISTRY }}/app:latest \
            --tag ${{ vars.REGISTRY }}/app:${{ github.sha }} \
            --tag ${{ vars.REGISTRY }}/app:latest \
            .
      - name: Push to registry
        run: |
          docker push ${{ vars.REGISTRY }}/app:${{ github.sha }}
          docker push ${{ vars.REGISTRY }}/app:latest

Leverage Docker layer caching by ordering your Dockerfile so that infrequently changing layers (dependencies) come before frequently changing layers (application code).

Secrets Management

Never bake secrets into images. Use Docker secrets in Swarm mode or pass them through environment variables at runtime. For Compose environments, use a .env file excluded from version control.

services:
  api:
    environment:
      - DATABASE_URL=${DATABASE_URL}
      - REDIS_URL=${REDIS_URL}
      - JWT_SECRET=${JWT_SECRET}

Containerizing Without Overhead

Docker is a developer tool first. A well-configured docker-compose.yml replaces pages of setup documentation. New team members run two commands — docker compose build and docker compose up — and have a fully running environment.

At SoniNow, we containerize every project from day one. Our web development services include Docker-based development environments, CI/CD pipeline setup, and production deployment configuration.

Containerize without friction. Work with SoniNow to make Docker work for your development workflow.