Modul 12: Deployment

Docker & Docker Compose

Nuxt-Apps containerisieren und mit Services orchestrieren

Warum Docker?

Docker sorgt dafür, dass deine Nuxt-App überall gleich läuft — auf deinem Laptop, im CI/CD und in Produktion. Keine "Funktioniert bei mir"-Probleme mehr.

🛤️

Rails-Vergleich

Wenn du Rails mit Docker deployest, kennst du das Pattern bereits: Multi-Stage Build, Produktions-Image mit minimaler Größe, Docker Compose für App + Datenbank. Das Vorgehen für Nuxt ist sehr ähnlich — nur ohne Datenbank-Container (es sei denn, du brauchst einen).

Dockerfile für Nuxt SSR

Dockerfile
# ============================================
# Stage 1: Dependencies installieren
# ============================================
FROM node:20-alpine AS deps

WORKDIR /app

# Nur Package-Dateien kopieren (Cache-optimiert)
COPY package.json package-lock.json ./

# Dependencies installieren
RUN npm ci

# ============================================
# Stage 2: Build
# ============================================
FROM node:20-alpine AS builder

WORKDIR /app

# Dependencies aus Stage 1 übernehmen
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Nuxt-App bauen
RUN npm run build

# ============================================
# Stage 3: Produktions-Image
# ============================================
FROM node:20-alpine AS runner

WORKDIR /app

# Nur den Build-Output kopieren
COPY --from=builder /app/.output ./.output

# Nicht als Root laufen
RUN addgroup --system --gid 1001 nuxt && \\
    adduser --system --uid 1001 nuxt
USER nuxt

# Port und Host setzen
ENV HOST=0.0.0.0
ENV PORT=3000
EXPOSE 3000

# Health-Check
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s \\
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1

# Server starten
CMD ["node", ".output/server/index.mjs"]
💡

Multi-Stage Build Vorteile

  • Kleineres Image — nur der Build-Output wird kopiert (~100 MB statt ~800 MB)
  • Besseres Caching — Dependencies werden separat gecacht
  • Sicherheit — keine Dev-Dependencies im Produktions-Image

.dockerignore

Verhindere, dass unnötige Dateien in den Build-Kontext kopiert werden:

.dockerignore
node_modules
.output
.nuxt
dist
.git
.gitignore
*.md
.env
.env.*
docker-compose*.yml
Dockerfile*

Docker Compose: Nuxt + json-server

Für eine vollständige Entwicklungs- und Produktionsumgebung orchestrierst du Nuxt und json-server mit Docker Compose:

docker-compose.yml
services:
  # ─── Nuxt App ───────────────────────────
  nuxt:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - '3000:3000'
    environment:
      - NODE_ENV=production
      - NUXT_PUBLIC_API_BASE=http://json-server:3001
    depends_on:
      json-server:
        condition: service_healthy
    restart: unless-stopped
    healthcheck:
      test: ['CMD', 'wget', '--spider', '-q', 'http://localhost:3000']
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 15s

  # ─── json-server (Mock-Backend) ─────────
  json-server:
    image: node:20-alpine
    working_dir: /data
    command: >
      sh -c 'npm install -g json-server &&
             json-server --watch db.json --port 3001 --host 0.0.0.0'
    ports:
      - '3001:3001'
    volumes:
      - ./db.json:/data/db.json
    restart: unless-stopped
    healthcheck:
      test: ['CMD', 'wget', '--spider', '-q', 'http://localhost:3001']
      interval: 10s
      timeout: 3s
      retries: 5
      start_period: 20s
Terminal
# Alles starten
docker compose up -d

# Logs anschauen
docker compose logs -f nuxt

# Nur json-server neu starten
docker compose restart json-server

# Alles stoppen und aufräumen
docker compose down

Umgebungsvariablen in Docker

.env
# Nuxt Runtime-Konfiguration
NUXT_PUBLIC_API_BASE=http://localhost:3001
NUXT_PUBLIC_APP_NAME=Kontakte-Manager

# Server-seitige Secrets
NUXT_JWT_SECRET=mein-geheimer-schluessel-hier
NUXT_SESSION_SECRET=noch-ein-geheimes-secret
nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    // Server-seitig (nicht im Client sichtbar!)
    jwtSecret: process.env.NUXT_JWT_SECRET || 'dev-fallback',
    sessionSecret: process.env.NUXT_SESSION_SECRET || 'dev-fallback',

    public: {
      // Im Client und Server verfügbar
      apiBase: process.env.NUXT_PUBLIC_API_BASE || 'http://localhost:3001',
      appName: process.env.NUXT_PUBLIC_APP_NAME || 'Meine App'
    }
  }
})
⚠️

Secrets in Docker

  • Nutze .env-Dateien nur lokal — nicht ins Git-Repo!
  • In Produktion: Secrets über die Hosting-Platform setzen
  • Variablen ohne NUXT_PUBLIC_ sind nur serverseitig
  • Docker Compose: env_file: .env oder direkt environment:

Health Checks

Ein Health-Check-Endpunkt zeigt, ob deine App korrekt läuft. Docker und Orchestratoren nutzen ihn zum Monitoring:

server/api/health.get.ts
export default defineEventHandler(() => {
  return {
    status: 'ok',
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    version: process.env.npm_package_version || 'unknown',
    environment: "production" || 'development'
  }
})
Dockerfile (HEALTHCHECK)
# Im Dockerfile:
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s \\
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1

# Docker prüft regelmäßig:
# - interval: Alle 30 Sekunden
# - timeout:  Maximal 3 Sekunden warten
# - start-period: 10 Sekunden Wartezeit nach Container-Start
# - retries: 3 (Standard) fehlgeschlagene Versuche = unhealthy
ℹ️

Health-Check Status

  • starting — Container startet gerade (start-period)
  • healthy — Health-Check erfolgreich
  • unhealthy — Mehrere fehlgeschlagene Checks

Mit docker ps siehst du den Health-Status aller Container. depends_on: condition: service_healthy wartet auf einen gesunden Container, bevor abhängige Services starten.

Zusammenfassung

💡

Docker-Checkliste

  • Multi-Stage Build — Deps → Build → Runner (kleines Image)
  • .dockerignore — node_modules, .output, .git ausschließen
  • Docker Compose — Nuxt + json-server orchestrieren
  • Umgebungsvariablen — NUXT_PUBLIC_ für Client, NUXT_ für Server
  • Health Checks/api/health Endpunkt + HEALTHCHECK
  • Non-Root User — Sicherheit: nicht als Root laufen