| name | docker-deployment |
| description | Docker containerization and deployment patterns for this Turborepo monorepo. Use when writing Dockerfiles, configuring docker-compose, optimizing images, or preparing production builds. Triggers on tasks involving containers, multi-stage builds, turbo prune, image optimization, or local orchestration. |
| frameworks | ["docker","turborepo"] |
| languages | ["dockerfile","yaml"] |
| category | deployment |
| updated | "2025-07-12T00:00:00.000Z" |
Docker Deployment Patterns
Quick Reference
| App | Dockerfile | Port | Health Check |
|---|
| Web (Next.js) | apps/web/Dockerfile | 3001 | wget http://localhost:3001/ |
| API (Laravel) | apps/laravel/Dockerfile | 8000 | wget http://localhost:8000/api/v1/health |
Monorepo Docker Strategy
This project uses Turborepo's turbo prune to create minimal Docker contexts per app, reducing build time and image size.
Web Dockerfile Pattern (Next.js Standalone)
FROM node:22-alpine AS base
RUN apk add --no-cache libc6-compat
RUN corepack enable && corepack prepare pnpm@10.27.0 --activate
# Stage 1: Prune monorepo for web app only
FROM base AS prepare
WORKDIR /app
COPY . .
RUN pnpm dlx turbo prune @repo/web --docker
# Stage 2: Install dependencies + build
FROM base AS builder
WORKDIR /app
COPY --from=prepare /app/out/json/ .
RUN pnpm install --frozen-lockfile
COPY --from=prepare /app/out/full/ .
# Build args for Next.js public env vars (baked at build time)
ARG NEXT_PUBLIC_APP_URL
ARG NEXT_PUBLIC_API_BASE_URL
ARG NEXT_PUBLIC_API_VERSION
RUN pnpm --filter @repo/web build
# Stage 3: Production runner
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/apps/web/.next/standalone ./
COPY --from=builder /app/apps/web/.next/static ./apps/web/.next/static
COPY --from=builder /app/apps/web/public ./apps/web/public
USER nextjs
EXPOSE 3001
ENV PORT=3001 HOSTNAME="0.0.0.0"
CMD ["node", "apps/web/server.js"]
Key patterns:
turbo prune @repo/web --docker creates isolated workspace with only web's dependencies
--docker flag splits output into out/json/ (package.json files) and out/full/ (source)
- Two-step copy: install deps from json first (cacheable), then copy source
- Next.js
output: "standalone" in next.config.ts enables minimal Node.js server
NEXT_PUBLIC_* vars are build args (not runtime env) — they're inlined during build
API Dockerfile Pattern (Laravel PHP-FPM + Nginx)
See apps/laravel/Dockerfile for the multi-stage pattern:
- Stage 1 (composer): Install PHP dependencies via Composer
- Stage 2 (runner): PHP-FPM + Nginx in a single container, serves on port 8000
composer install --no-dev --optimize-autoloader for production
php artisan optimize bakes config/route caches into the image
- Health check via
wget http://localhost:8000/api/v1/health
Key patterns:
wget used for health check (alpine-compatible)
- All Laravel env vars are runtime (not build args) — set via docker-compose or ECS task definition
APP_KEY must be a stable secret; never auto-generate in container startup
- Nginx config proxies port 80 → PHP-FPM on the Unix socket
Docker Compose (Local Production Testing)
services:
web:
build:
context: .
dockerfile: apps/web/Dockerfile
args:
NEXT_PUBLIC_APP_URL: http://localhost:3001
NEXT_PUBLIC_API_BASE_URL: http://localhost:8000/api
NEXT_PUBLIC_API_VERSION: v1
ports:
- "3001:3001"
environment:
- NODE_ENV=production
- INTERNAL_API_BASE_URL=http://api:80/api
depends_on:
api:
condition: service_healthy
networks:
- app-network
api:
build:
context: apps/laravel
dockerfile: Dockerfile
ports:
- "8000:80"
environment:
- APP_ENV=production
- APP_KEY=${APP_KEY}
- DB_HOST=mysql
- DB_DATABASE=${DB_DATABASE}
- DB_USERNAME=${DB_USERNAME}
- DB_PASSWORD=${DB_PASSWORD}
- CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS}
- SANCTUM_STATEFUL_DOMAINS=${SANCTUM_STATEFUL_DOMAINS}
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:80/api/v1/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
depends_on:
mysql:
condition: service_healthy
networks:
- app-network
networks:
app-network:
driver: bridge
Key patterns:
- Web depends on
api with service_healthy condition
- API env vars from
.env file (not hardcoded)
INTERNAL_API_BASE_URL=http://api:80/api routes SSR requests through Docker network
- Shared bridge network for service-to-service communication
- Web build args for Next.js public variables (
NEXT_PUBLIC_* baked at build time)
Common Commands
docker compose up --build
docker compose build web
docker compose build api
docker compose --env-file .env.production up
docker compose logs -f web
docker compose logs -f api
docker compose exec api sh
docker compose exec api php artisan migrate
docker compose down --volumes --rmi all
Image Optimization Rules
- Always use
node:22-alpine — ~180MB vs ~1GB for full node image
- Multi-stage builds — Builder stage has dev deps, runner has only production artifacts
- Non-root user — Create and switch to
nodejs or nextjs user (uid 1001)
- Layer caching — Copy
package.json files first, install deps, then copy source
turbo prune --docker — Isolate workspace packages to minimize Docker context
.dockerignore — Exclude node_modules, .next, dist, .git, test files
- Frozen lockfile —
pnpm install --frozen-lockfile for reproducible builds
Build Args vs Runtime Env
| Variable Type | When Set | Example |
|---|
Build args (ARG) | Docker build time | NEXT_PUBLIC_* vars (inlined in JS bundle) |
Runtime env (ENV) | Container start | APP_KEY, DATABASE_URL, CORS_ALLOWED_ORIGINS |
Rule: Next.js NEXT_PUBLIC_* variables MUST be build args. Laravel variables are runtime env.
Health Check Patterns
# Laravel API: wget-based (alpine-compatible)
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:80/api/v1/health || exit 1
# Web (Next.js): wget-based
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3001/ || exit 1
--start-period=40s gives the app time to boot before checking
--retries=3 allows transient failures
- Both docker-compose and EC2 ASG deployments respect HEALTHCHECK directives