Docker for Frontend Developers: A Practical Guide
Docker for Frontend Developers: A Practical Guide
Docker isn't just for backend developers. As a frontend developer, understanding Docker can revolutionize your workflow, ensure consistency across environments, and simplify deployments.
Why Docker for Frontend?
- Consistent environments - Same setup across all machines
- Easy onboarding - New developers get started quickly
- Isolated dependencies - No more "works on my machine"
- Simplified deployments - Ship containers, not configurations
- CI/CD integration - Automated testing and deployment
Docker Basics
Key Concepts
Image - A blueprint for containers (like a class)
Container - A running instance of an image (like an object)
Dockerfile - Instructions to build an image
Registry - Storage for images (Docker Hub, GitHub Registry)
Common Commands
# Build an image
docker build -t my-app .
# Run a container
docker run -p 3000:3000 my-app
# List running containers
docker ps
# Stop a container
docker stop container_id
# Remove a container
docker rm container_id
# List images
docker images
# Remove an image
docker rmi image_id
# View logs
docker logs container_id
# Execute command in container
docker exec -it container_id sh
Dockerizing a React Application
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
# Dockerfile
# Stage 1: Build
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: 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;"]
Nginx Configuration
# nginx.conf
events {
worker_connections 1024;
}
http {
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
}
}
Dockerizing a Next.js Application
Development Dockerfile
# Dockerfile.dev
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]
Production Dockerfile
# Dockerfile
FROM node:18-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
# Builder
FROM node:18-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED 1
RUN npm run build
# Runner
FROM node:18-alpine 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"]
Next.js Configuration for Standalone
// next.config.js
module.exports = {
output: 'standalone',
}
Docker Compose for Development
Basic Docker Compose
# docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
- CHOKIDAR_USEPOLLING=true
Full Stack Development Environment
# docker-compose.yml
version: '3.8'
services:
frontend:
build:
context: ./frontend
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
volumes:
- ./frontend:/app
- /app/node_modules
environment:
- API_URL=http://api:4000
depends_on:
- api
api:
build:
context: ./api
dockerfile: Dockerfile.dev
ports:
- "4000:4000"
volumes:
- ./api:/app
- /app/node_modules
environment:
- DATABASE_URL=postgres://user:password@db:5432/mydb
depends_on:
- db
db:
image: postgres:14-alpine
ports:
- "5432:5432"
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=mydb
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:alpine
ports:
- "6379:6379"
volumes:
postgres_data:
Running with Docker Compose
# Start all services
docker-compose up
# Start in background
docker-compose up -d
# Stop all services
docker-compose down
# Rebuild and start
docker-compose up --build
# View logs
docker-compose logs -f
# Scale a service
docker-compose up --scale api=3
Environment Variables
Using .env Files
# .env
REACT_APP_API_URL=https://api.example.com
REACT_APP_ENV=production
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
env_file:
- .env
Passing Build Arguments
# Dockerfile
ARG API_URL
ENV REACT_APP_API_URL=$API_URL
RUN npm run build
# docker-compose.yml
services:
app:
build:
context: .
args:
- API_URL=https://api.example.com
Optimizing Docker Images
Use Smaller Base Images
# Instead of node:18 (900MB+)
FROM node:18-alpine (170MB)
# Even smaller for production
FROM nginx:alpine (50MB)
Minimize Layers
# Bad - Multiple layers
RUN npm install
RUN npm run build
RUN npm run test
# Good - Single layer
RUN npm install && \
npm run build && \
npm run test
Leverage Cache
# Order matters for caching
# Put frequently changing files last
# Good - Dependencies cached unless package.json changes
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
Multi-stage Builds
# Build dependencies are discarded
FROM node:18-alpine AS builder
WORKDIR /app
COPY . .
RUN npm install && npm run build
# Final image only has production files
FROM nginx:alpine
COPY --from=builder /app/build /usr/share/nginx/html
# Image size: ~25MB instead of ~1GB
.dockerignore
# .dockerignore
node_modules
npm-debug.log
build
.git
.gitignore
.env.local
.env.development.local
.env.test.local
.env.production.local
README.md
.DS_Store
coverage
.idea
Health Checks
# Dockerfile
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
# docker-compose.yml
services:
app:
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
CI/CD Integration
GitHub Actions Example
# .github/workflows/deploy.yml
name: Build and Deploy
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 Docker Hub
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:
context: .
push: true
tags: myorg/my-app:latest
cache-from: type=gha
cache-to: type=gha,mode=max
Common Issues and Solutions
Hot Reload Not Working
# docker-compose.yml
services:
app:
environment:
- CHOKIDAR_USEPOLLING=true
- WATCHPACK_POLLING=true
Permission Issues
# Fix npm permission issues
RUN mkdir -p /app/node_modules && chown -R node:node /app
USER node
Memory Issues in Node
# Increase Node memory limit
ENV NODE_OPTIONS="--max-old-space-size=4096"
Network Issues
# Access host from container
# On Linux: host.docker.internal not available by default
docker run --add-host=host.docker.internal:host-gateway my-app
Best Practices
1. Use Specific Versions
# Bad
FROM node:latest
# Good
FROM node:18.17.0-alpine
2. Run as Non-root User
FROM node:18-alpine
RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S appuser -G appgroup
WORKDIR /app
COPY --chown=appuser:appgroup . .
USER appuser
CMD ["npm", "start"]
3. Scan for Vulnerabilities
# Scan image for vulnerabilities
docker scout cves my-app:latest
# Using Trivy
trivy image my-app:latest
4. Use Docker Compose Override
# docker-compose.yml - Base configuration
services:
app:
image: my-app
# docker-compose.override.yml - Local overrides (not committed)
services:
app:
volumes:
- .:/app
environment:
- DEBUG=true
Debugging Containers
# Access container shell
docker exec -it container_id sh
# View container details
docker inspect container_id
# Follow logs in real-time
docker logs -f container_id
# Copy files from container
docker cp container_id:/app/logs ./logs
# Check resource usage
docker stats
Conclusion
Docker provides frontend developers with powerful tools for creating consistent, reproducible environments. By mastering these Docker concepts and patterns, you can eliminate environment-related issues and focus on building great user interfaces.
Quick Reference
- Use multi-stage builds for smaller production images
- Leverage Docker Compose for local development
- Implement health checks for reliability
- Follow security best practices
- Optimize for caching in CI/CD pipelines
Start containerizing your frontend applications today and experience the benefits of consistent, reliable deployments!