Docker for Frontend Developers: A Practical Guide
Docker for Frontend Developers: A Practical Guide
Docker isn't just for backend developers. Learn how to containerize frontend applications effectively.
Why Docker for Frontend?
- Consistent environments - Works the same everywhere
- Easy onboarding - New developers start quickly
- CI/CD simplified - Reproducible builds
- Easy deployment - Ship anywhere
Basic Dockerfile
# Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
Multi-Stage Build for Production
# Build stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Optimizing Images
Use Alpine Images
# Larger
FROM node:18
# Smaller
FROM node:18-alpine
Layer Caching
# Good: Copy package files first
COPY package*.json ./
RUN npm install
# Then copy source code
COPY . .
RUN npm run build
# Bad: Copy everything first
COPY . .
RUN npm install
RUN npm run build
.dockerignore
node_modules
npm-debug.log
Dockerfile
.dockerignore
.git
.gitignore
.env.local
.env.*.local
coverage
.nyc_output
Development with Docker
Docker Compose for Development
# docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
target: development
volumes:
- .:/app
- /app/node_modules
ports:
- "3000:3000"
environment:
- NODE_ENV=development
command: npm run dev
Development Dockerfile
# Dockerfile
FROM node:18-alpine AS development
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]
# Production stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
FROM nginx:alpine AS production
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Environment Variables
# docker-compose.yml
version: '3.8'
services:
app:
build: .
environment:
- API_URL=http://api:4000
- NODE_ENV=production
env_file:
- .env
# Dockerfile - Build-time args
ARG API_URL
ENV API_URL=$API_URL
# Build with args
# docker build --build-arg API_URL=https://api.example.com .
NGINX Configuration
# nginx.conf
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript;
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# SPA fallback
location / {
try_files $uri $uri/ /index.html;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
}
}
Next.js with Docker
# Dockerfile for Next.js
FROM node:18-alpine AS base
# Install dependencies only when needed
FROM base AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED 1
RUN npm run build
# Production image
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
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
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"
CMD ["node", "server.js"]
React with Docker
# Dockerfile for React
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production
FROM nginx:alpine
COPY --from=builder /app/build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Vue with Docker
# Dockerfile for Vue
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
CI/CD Integration
GitHub Actions
# .github/workflows/docker.yml
name: Docker Build and Push
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v4
with:
push: true
tags: myapp:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
Docker Commands Cheat Sheet
# Build image
docker build -t myapp:latest .
# Build with no cache
docker build --no-cache -t myapp:latest .
# Run container
docker run -p 3000:3000 myapp:latest
# Run with environment variables
docker run -p 3000:3000 -e API_URL=http://api myapp:latest
# Run with compose
docker-compose up -d
# View logs
docker logs <container_id>
# Shell into container
docker exec -it <container_id> sh
# Stop all containers
docker stop $(docker ps -aq)
# Remove all images
docker rmi $(docker images -q)
# Prune everything
docker system prune -a
Best Practices
- Use multi-stage builds for smaller images
- Cache layers by ordering instructions correctly
- Use .dockerignore to reduce build context
- Run as non-root user for security
- Use specific version tags not
latest - Scan images for vulnerabilities
- Keep images small for faster deployments
Conclusion
Docker simplifies frontend development by providing consistent environments from development to production. Multi-stage builds and proper configuration lead to efficient, secure container images.