S SvelteKurs
M11 · Deployment

Docker

Die App in ein reproduzierbares Image packen — genau das Setup, mit dem dieser Kurs deployed wird.

Warum Multi-Stage?

Wir bauen in zwei Stufen: Die builder-Stufe installiert alle Abhängigkeiten und erzeugt build/. Die schlanke runner-Stufe übernimmt nur das Nötige und startet den Server. Das hält das finale Image klein und sauber.

Dockerfile bash
# ── Stage 1: build (adapter-node → build/) ──────────────────
FROM node:24-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# ── Stage 2: production runner ──────────────────────────────
FROM node:24-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
ENV HOST=0.0.0.0

COPY --from=builder /app/build ./build
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/drizzle ./drizzle
COPY --from=builder /app/drizzle.config.ts ./drizzle.config.ts
COPY entrypoint.sh ./entrypoint.sh
RUN chmod +x ./entrypoint.sh

EXPOSE 3000
CMD ["./entrypoint.sh"]

🛤️ Rails-Vergleich: Dockerfile & db:migrate beim Start

Das Muster kennst du: bauen, dann Server starten. Unser entrypoint.sh führt vor dem Start die Migrationen aus — genau wie ein bin/rails db:migrate im Release-Step.

Analogie ruby
# Rails-Dockerfile: sehr ähnliche Idee
FROM ruby:3.3-alpine
# ... bundle install, assets:precompile ...
CMD ["bin/rails", "server", "-b", "0.0.0.0"]
# Migrationen beim Start: bin/rails db:migrate (wie unser entrypoint)

Der Entrypoint: erst migrieren, dann starten

entrypoint.sh bash
#!/bin/sh
set -e
echo "Running migrations…"
node_modules/.bin/drizzle-kit migrate
if [ "$SEED_ON_START" = "true" ]; then node scripts/seed.js; fi
exec node build

So ist die Datenbank beim ersten Request garantiert auf dem aktuellen Stand. Mit SEED_ON_START=true werden zusätzlich Demo-Daten eingespielt.

Lokal testen mit Compose

docker-compose.yml startet Postgres und die App zusammen — ideal, um das Produktions-Image vor dem Deploy zu prüfen:

docker-compose.yml bash
services:
  db:
    image: postgres:16-alpine
    environment: { POSTGRES_PASSWORD: postgres, POSTGRES_DB: devnotes }
  app:
    build: .
    depends_on: [db]
    environment:
      DATABASE_URL: postgres://postgres:postgres@db:5432/devnotes
      ORIGIN: http://localhost:3000
    ports: ['3000:3000']
bash
docker compose up --build   # DB + App lokal starten → localhost:3000

⚠️ DATABASE_URL zur Laufzeit

Der DB-Client liest die URL über $env/dynamic/private — also zur Laufzeit, nicht beim Build. Dadurch funktioniert dasselbe Image lokal und in der Cloud; nur die Env-Variable unterscheidet sich.

Zusammenfassung

💡 Das Wichtigste

  • Multi-Stage-Build: bauen in Stage 1, schlank ausliefern in Stage 2.
  • Der Container hört auf Port 3000 (HOST=0.0.0.0).
  • Migrationen laufen im Entrypoint vor dem Serverstart.

🎯 Probier es selbst

Führe docker compose up --build aus und öffne http://localhost:3000. Registriere ein Konto in DevNotes — die Daten landen im Postgres-Container.