# macOS / Windows: Install Docker Desktop
# Linux:
-weight: 500;">curl -fsSL https://get.-weight: 500;">docker.com | sh
# macOS / Windows: Install Docker Desktop
# Linux:
-weight: 500;">curl -fsSL https://get.-weight: 500;">docker.com | sh
# macOS / Windows: Install Docker Desktop
# Linux:
-weight: 500;">curl -fsSL https://get.-weight: 500;">docker.com | sh
# Start from an official base image
FROM node:20-alpine # Set working directory
WORKDIR /app # Copy dependency files first (for caching)
COPY package*.json ./ # Install dependencies
RUN -weight: 500;">npm ci --only=production # Copy source code
COPY . . # Expose port
EXPOSE 3000 # Start the app
CMD ["node", "server.js"]
# Start from an official base image
FROM node:20-alpine # Set working directory
WORKDIR /app # Copy dependency files first (for caching)
COPY package*.json ./ # Install dependencies
RUN -weight: 500;">npm ci --only=production # Copy source code
COPY . . # Expose port
EXPOSE 3000 # Start the app
CMD ["node", "server.js"]
# Start from an official base image
FROM node:20-alpine # Set working directory
WORKDIR /app # Copy dependency files first (for caching)
COPY package*.json ./ # Install dependencies
RUN -weight: 500;">npm ci --only=production # Copy source code
COPY . . # Expose port
EXPOSE 3000 # Start the app
CMD ["node", "server.js"]
-weight: 500;">docker build -t my-app .
-weight: 500;">docker run -p 3000:3000 my-app
-weight: 500;">docker build -t my-app .
-weight: 500;">docker run -p 3000:3000 my-app
-weight: 500;">docker build -t my-app .
-weight: 500;">docker run -p 3000:3000 my-app
# Build an image
-weight: 500;">docker build -t app-name . # Run a container
-weight: 500;">docker run -p 8080:3000 app-name # Run in background (detached)
-weight: 500;">docker run -d -p 8080:3000 app-name # List running containers
-weight: 500;">docker ps # Stop a container
-weight: 500;">docker -weight: 500;">stop container-id # View logs
-weight: 500;">docker logs container-id -f # Shell into a running container
-weight: 500;">docker exec -it container-id sh # Clean up everything
-weight: 500;">docker system prune -a
# Build an image
-weight: 500;">docker build -t app-name . # Run a container
-weight: 500;">docker run -p 8080:3000 app-name # Run in background (detached)
-weight: 500;">docker run -d -p 8080:3000 app-name # List running containers
-weight: 500;">docker ps # Stop a container
-weight: 500;">docker -weight: 500;">stop container-id # View logs
-weight: 500;">docker logs container-id -f # Shell into a running container
-weight: 500;">docker exec -it container-id sh # Clean up everything
-weight: 500;">docker system prune -a
# Build an image
-weight: 500;">docker build -t app-name . # Run a container
-weight: 500;">docker run -p 8080:3000 app-name # Run in background (detached)
-weight: 500;">docker run -d -p 8080:3000 app-name # List running containers
-weight: 500;">docker ps # Stop a container
-weight: 500;">docker -weight: 500;">stop container-id # View logs
-weight: 500;">docker logs container-id -f # Shell into a running container
-weight: 500;">docker exec -it container-id sh # Clean up everything
-weight: 500;">docker system prune -a
# -weight: 500;">docker-compose.yml
version: "3.9" services: app: build: . ports: - "3000:3000" environment: - DATABASE_URL=postgres://user:pass@db:5432/mydb depends_on: - db - redis db: image: postgres:16 environment: POSTGRES_USER: user POSTGRES_PASSWORD: pass POSTGRES_DB: mydb volumes: - postgres_data:/var/lib/postgresql/data redis: image: redis:7-alpine volumes: postgres_data:
# -weight: 500;">docker-compose.yml
version: "3.9" services: app: build: . ports: - "3000:3000" environment: - DATABASE_URL=postgres://user:pass@db:5432/mydb depends_on: - db - redis db: image: postgres:16 environment: POSTGRES_USER: user POSTGRES_PASSWORD: pass POSTGRES_DB: mydb volumes: - postgres_data:/var/lib/postgresql/data redis: image: redis:7-alpine volumes: postgres_data:
# -weight: 500;">docker-compose.yml
version: "3.9" services: app: build: . ports: - "3000:3000" environment: - DATABASE_URL=postgres://user:pass@db:5432/mydb depends_on: - db - redis db: image: postgres:16 environment: POSTGRES_USER: user POSTGRES_PASSWORD: pass POSTGRES_DB: mydb volumes: - postgres_data:/var/lib/postgresql/data redis: image: redis:7-alpine volumes: postgres_data:
-weight: 500;">docker compose up -d
-weight: 500;">docker compose up -d
-weight: 500;">docker compose up -d
node_modules
.-weight: 500;">git
.env
*.log
dist
node_modules
.-weight: 500;">git
.env
*.log
dist
node_modules
.-weight: 500;">git
.env
*.log
dist
# Stage 1: Build
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN -weight: 500;">npm ci
COPY . .
RUN -weight: 500;">npm run build # Stage 2: Production (much smaller)
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]
# Stage 1: Build
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN -weight: 500;">npm ci
COPY . .
RUN -weight: 500;">npm run build # Stage 2: Production (much smaller)
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]
# Stage 1: Build
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN -weight: 500;">npm ci
COPY . .
RUN -weight: 500;">npm run build # Stage 2: Production (much smaller)
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]
# Wrong
ENV DATABASE_PASSWORD=supersecret # Right: use environment variables at runtime
-weight: 500;">docker run -e DATABASE_PASSWORD=$DB_PASS my-app
# Wrong
ENV DATABASE_PASSWORD=supersecret # Right: use environment variables at runtime
-weight: 500;">docker run -e DATABASE_PASSWORD=$DB_PASS my-app
# Wrong
ENV DATABASE_PASSWORD=supersecret # Right: use environment variables at runtime
-weight: 500;">docker run -e DATABASE_PASSWORD=$DB_PASS my-app
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \ CMD -weight: 500;">curl -f http://localhost:3000/health || exit 1
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \ CMD -weight: 500;">curl -f http://localhost:3000/health || exit 1
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \ CMD -weight: 500;">curl -f http://localhost:3000/health || exit 1 - "Works on my machine" disasters
- Manual dependency installation on every server
- Environment differences between dev, staging, and production
- Hours debugging "but it worked locally!" - Railway.app — push Dockerfile, it deploys (free tier available)
- Render.com — Docker support, free tier
- Fly.io — excellent free tier, global edge
- GitHub Actions + VPS — $5/month VPS, full control - Never SSH into a production container to make changes
- Fix → rebuild → redeploy
- Logs go to stdout/stderr, not files
- State goes in volumes or external databases