Docker für Webentwickler — Mein Deployment-Workflow als Freelancer
Wie ich Docker für meine Webentwicklung-Projekte einsetze: Container-Entwicklung, CI/CD-Pipeline und automatisierte Deployments auf eigener Infrastruktur.
Docker ist für mich kein Nice-to-have mehr — es ist das Fundament meiner gesamten Entwicklungs- und Deployment-Infrastruktur. Jedes Projekt, das ich ausliefere, läuft in Docker-Containern, wird über eine CI/CD-Pipeline gebaut und automatisch auf meiner Server-Infrastruktur deployed.
In diesem Beitrag zeige ich dir meinen genauen Workflow — von der lokalen Entwicklung bis zum Production-Deployment.
Warum Docker?
Bevor ich Docker genutzt habe, sah mein Deployment so aus:
- Per FTP Dateien auf den Server kopieren
- Hoffen, dass die PHP/Konfiguration auf dem Server mit der lokalen Umgebung übereinstimmt
- Bei Fehlern: stundenlanges Debuggen, warum es lokal funktioniert, aber auf dem Server nicht
Docker löst dieses Problem radikal: Die Umgebung, in der du entwickelst, ist identisch mit der Umgebung, in der die Website läuft. Kein „Es funktioniert aber auf meinem Computer!“ mehr.
Die Vorteile für mich als Freelancer:
- Reproduzierbarkeit — Jedes Projekt hat eine exakte Definition seiner Laufzeitumgebung
- Isolation — Projekte beeinflussen sich nicht gegenseitig
- Deployment-Sicherheit — Was lokal läuft, läuft auch auf dem Server
- Skalierbarkeit — Ein Docker-Container lässt sich horizontal skalieren, falls nötig
Mein Docker-Setup pro Projekt
Jedes Projekt bekommt ein docker-compose.yml im Root-Verzeichnis. Hier ein Beispiel für ein typisches Angular + Astro + Vue.js Projekt:
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
caddy:
image: caddy:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
depends_on:
- app
volumes:
caddy_data:
Warum Caddy? Weil Caddy automatische SSL-Zertifikate über Let’s Encrypt ausstellt. Kein manuelles Zertifikatsmanagement mehr.
Der Dockerfile-Ansatz
Ich verwende zwei Dockerfiles: eins für die Entwicklung und eins für die Produktion.
Dockerfile.dev — Für die lokale Entwicklung:
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
Dockerfile.prod — Für den Production-Build mit Multi-Stage-Build:
# Stage 1: Build
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Production
FROM nginx:stable-alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Der Multi-Stage-Build sorgt dafür, dass das finale Image nur das Nötigste enthält — kein Node.js, keine Entwicklungs-Abhängigkeiten. Das spart Speicherplatz und reduziert die Angriffsfläche.
CI/CD mit GitHub Actions
Mein Deployment läuft voll automatisiert über GitHub Actions. Sobald ich Code in den main-Branch pushe, passiert Folgendes:
- Tests laufen (TypeScript-Check, Linting, Build-Test)
- Docker-Image wird gebaut mit dem Production-Dockerfile
- Image wird in die GitHub Container Registry gepusht
- SSH in den Server und Deployment-Skript ausführen
Hier ist meine .github/workflows/deploy.yml:
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ghcr.io/miran-arnaut/projektname:latest
- name: Deploy to server
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_KEY }}
script: |
cd /opt/projekte/projektname
docker compose pull
docker compose up -d --force-recreate
docker system prune -f
Deployment-Infrastruktur
Mein Server-Setup sieht so aus:
- Ein Virtual Private Server (VPS) mit Ubuntu 24.04, 4 GB RAM, 2 CPUs
- Docker und Docker Compose installiert
- Caddy als Reverse Proxy mit automatischen SSL-Zertifikaten
- Cloudflare Tunnel für DDoS-Schutz und schnelleres Routing
- Automatische Backups via Cron-Job (tägliches Backup der Docker-Volumes)
Auf dem Server läuft ein docker-compose.yml, das alle laufenden Projekte verwaltet:
version: '3.8'
services:
projekt-a:
image: ghcr.io/miran-arnaut/projekt-a:latest
restart: always
networks:
- web
projekt-b:
image: ghcr.io/miran-arnaut/projekt-b:latest
restart: always
networks:
- web
caddy:
image: caddy:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
networks:
- web
networks:
web:
external: true
volumes:
caddy_data:
Jedes Projekt läuft isoliert in seinem eigenen Container. Caddy routet die Domain zum richtigen Container.
Monitoring
Deployment ist eine Sache — zu wissen, dass alles läuft, eine andere. Ich verwende:
- Uptime Kuma (selbst gehostet) — pingt alle 5 Minuten jede Website an
- Docker Healthchecks — jeder Container hat einen Healthcheck, der automatisch Neustarts auslöst
- Caddy Logs — werden automatisch rotiert und für 30 Tage aufbewahrt
- E-Mail-Benachrichtigungen — wenn ein Container crasht oder eine Website nicht erreichbar ist
Was ich gelernt habe
Docker ist am Anfang eine Lernkurve. Hier sind die Dinge, die ich gerne früher gewusst hätte:
- Verwende Named Volumes, nicht Bind Mounts für Datenbanken. Sonst verlierst du Daten beim Neustart.
- Setze immer Ressourcen-Limits.
--memory="512m"verhindert, dass ein fehlerhafter Container den ganzen Server lahmlegt. - Dokumentiere deine Docker-Befehle. Wenn du ein Projekt sechs Monate nicht anfasst, weißt du nicht mehr, wie man es startet.
- Nutze Docker Networks. Isolierte Netzwerke zwischen Containern sind sicherer und sauberer.
- Prune regelmäßig.
docker system prune -fin der CI/CD-Pipeline hält den Server sauber.
Lohnt sich der Aufwand?
Absolut. Ja, Docker bedeutet zusätzliche Komplexität am Anfang. Aber die Zeit, die ich früher mit manuellen Deployments, Konfigurationsproblemen und „Es funktioniert bei mir”-Debugging verbracht habe, ist jetzt praktisch Null.
Jedes neue Projekt bekommt in 15 Minuten sein Docker-Setup. Jedes Deployment läuft automatisch. Jeder Server ist identisch zur Entwicklungsumgebung.
Für mich als Freelancer, der mehrere Projekte gleichzeitig betreut, ist Docker der Unterschied zwischen chaotischem und kontrolliertem Arbeiten.
Du willst dein nächstes Projekt mit Docker und CI/CD aufsetzen? Ich helfe dir dabei. Schreib mir an arnaut@miran.at.