Docker und Container für Einsteiger
"Auf meinem Rechner funktioniert es" -- diesen Satz hat jeder Entwickler schon einmal gesagt (oder gehört). Docker löst genau dieses Problem. Es verpackt deine Anwendung samt aller Abhängigkeiten in einen Container, der überall gleich läuft: auf deinem Laptop, im CI-System, auf dem Server.
In diesem Beitrag erkläre ich dir Docker von Grund auf. Keine Vorkenntnisse nötig -- wir starten bei null und arbeiten uns bis zu einem produktionsreifen Setup vor.
Was sind Container?
Stell dir einen Umzugskarton vor. Darin ist alles, was du brauchst: deine Anwendung, die richtige Node.js-Version, alle Libraries, die Konfiguration. Du kannst diesen Karton nehmen und überall hinstellen -- er funktioniert immer gleich.
Das ist im Wesentlichen ein Container.
Container vs. Virtuelle Maschinen
Eine virtuelle Maschine (VM) simuliert einen kompletten Computer -- inklusive Betriebssystem. Das braucht Ressourcen und dauert.
Ein Container teilt sich das Betriebssystem mit dem Host und startet in Millisekunden. Er ist leichtgewichtig, schnell und effizient.
| Eigenschaft | VM | Container |
|---|---|---|
| Startzeit | Minuten | Sekunden |
| Grösse | Gigabytes | Megabytes |
| Isolation | Vollständig | Prozess-Level |
| Ressourcenverbrauch | Hoch | Niedrig |
| Portabilität | Begrenzt | Sehr hoch |
Docker installieren
macOS
Lade Docker Desktop herunter und installiere es. Das ist der einfachste Weg.
Linux (Ubuntu/Debian)
# Docker installieren
curl -fsSL https://get.docker.com | sh
# Deinen User zur Docker-Gruppe hinzufuegen
sudo usermod -aG docker $USER
# Neustart der Shell noetig
Windows
Docker Desktop für Windows mit WSL2-Backend. Funktioniert gut, braucht aber etwas Einrichtung.
Dein erstes Docker-Projekt
Schritt 1: Eine einfache Node.js-App
Erstelle eine Datei app.js:
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Servus aus dem Docker Container!\n');
});
server.listen(3000, () => {
console.log('Server laeuft auf Port 3000');
});
Schritt 2: Dockerfile erstellen
Das Dockerfile beschreibt, wie dein Container gebaut wird:
# Base Image: Node.js 20 auf Alpine Linux (klein und schnell)
FROM node:20-alpine
# Arbeitsverzeichnis im Container
WORKDIR /app
# Package-Dateien kopieren und Dependencies installieren
COPY package*.json ./
RUN npm ci --only=production
# Anwendungscode kopieren
COPY . .
# Port freigeben
EXPOSE 3000
# Startbefehl
CMD ["node", "app.js"]
Schritt 3: Image bauen
docker build -t mein-startup-app .
Dieser Befehl:
- Liest das Dockerfile
- Lädt das Base Image herunter
- Führt die Anweisungen aus
- Erstellt ein Image mit dem Namen
mein-startup-app
Schritt 4: Container starten
docker run -p 3000:3000 mein-startup-app
Öffne http://localhost:3000 im Browser -- du solltest "Servus aus dem Docker Container!" sehen.
Dockerfile Best Practices
Multi-Stage Builds
Für Produktionsimages willst du so wenig wie möglich im Container haben. Multi-Stage Builds helfen dabei:
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Production
FROM node:20-alpine AS production
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY --from=builder /app/dist ./dist
EXPOSE 3000
USER node
CMD ["node", "dist/server.js"]
Das Ergebnis: Ein schlankes Production-Image ohne Build-Tools und Dev-Dependencies.
.dockerignore
Erstelle eine .dockerignore-Datei, um unnötige Dateien aus dem Image auszuschliessen:
node_modules
.git
.env
*.md
.github
coverage
tests
Sicherheit
- Nutze einen nicht-root User:
USER nodeim Dockerfile - Verwende spezifische Image-Tags:
node:20.11-alpinestattnode:latest - Scanne Images auf Schwachstellen:
docker scout quickview mein-startup-app - Halte Images klein: Alpine-basierte Images sind deutlich kleiner
Docker Compose -- Mehrere Container orchestrieren
Deine Anwendung besteht vermutlich nicht nur aus einem Server. Du brauchst eine Datenbank, vielleicht Redis für Caching, einen Reverse Proxy. Docker Compose startet all das mit einem Befehl.
docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://postgres:geheim@db:5432/startup
- REDIS_URL=redis://cache:6379
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: startup
POSTGRES_USER: postgres
POSTGRES_PASSWORD: geheim
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
cache:
image: redis:7-alpine
ports:
- "6379:6379"
adminer:
image: adminer
ports:
- "8080:8080"
depends_on:
- db
volumes:
postgres_data:
Starten und Stoppen
# Alle Services starten
docker compose up -d
# Logs ansehen
docker compose logs -f app
# Status pruefen
docker compose ps
# Alles stoppen
docker compose down
# Alles stoppen und Daten loeschen
docker compose down -v
Docker für die lokale Entwicklung
Development-Modus mit Hot Reload
Für die Entwicklung willst du, dass Änderungen am Code sofort im Container sichtbar sind. Das geht mit Volume Mounts:
# docker-compose.dev.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
volumes:
- ./src:/app/src # Code wird live synchronisiert
- /app/node_modules # node_modules im Container behalten
environment:
- NODE_ENV=development
command: npm run dev # Startet mit Hot Reload
# Development-Modus starten
docker compose -f docker-compose.dev.yml up
Development Dockerfile
# Dockerfile.dev
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install # Alle Dependencies (inkl. devDependencies)
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]
Docker in der CI/CD Pipeline
Docker und CI/CD sind ein Dreamteam. Du baust dein Image in der Pipeline und deployest es auf den Server.
GitHub Actions mit Docker
name: Build and Deploy
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push
uses: docker/build-push-action@v5
with:
push: true
tags: |
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
Docker auf Hetzner deployen
Für österreichische Startups ist Hetzner Cloud eine beliebte Wahl. Hier ist ein einfaches Deployment-Setup:
Server vorbereiten
# Auf dem Hetzner-Server
sudo apt update && sudo apt install -y docker.io docker-compose-v2
sudo systemctl enable docker
docker-compose.production.yml
version: '3.8'
services:
app:
image: ghcr.io/dein-startup/dein-produkt:latest
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=${DATABASE_URL}
restart: always
deploy:
resources:
limits:
memory: 512M
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
restart: always
caddy:
image: caddy:2-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
restart: always
volumes:
postgres_data:
caddy_data:
Caddyfile (automatisches HTTPS)
dein-startup.at {
reverse_proxy app:3000
}
Caddy kümmert sich automatisch um SSL-Zertifikate via Let's Encrypt. Kein Nginx-Konfigurieren, kein Certbot -- einfach die Domain angeben und fertig.
Nützliche Docker-Befehle
# Laufende Container anzeigen
docker ps
# Alle Container anzeigen (auch gestoppte)
docker ps -a
# In einen laufenden Container rein
docker exec -it container_name sh
# Logs eines Containers ansehen
docker logs -f container_name
# Ungenutzte Images/Container/Volumes aufraeumen
docker system prune -a
# Image-Groesse anzeigen
docker images
# Container stoppen und loeschen
docker stop container_name && docker rm container_name
Häufige Fehler und Lösungen
"Port already in use"
Ein anderer Prozess nutzt bereits den Port. Lösung:
# Prozess auf Port 3000 finden
lsof -i :3000
# Anderen Port im docker-compose.yml verwenden
ports:
- "3001:3000"
"COPY failed: file not found"
Deine .dockerignore schliesst die Datei aus, oder der Pfad ist falsch. Prüfe beides.
Container startet und stoppt sofort
Dein Startbefehl hat einen Fehler. Prüfe die Logs:
docker logs container_name
Image ist zu gross
Nutze Alpine-basierte Images und Multi-Stage Builds. Typische Grössen:
| Image | Grösse |
|---|---|
| node:20 | ~1 GB |
| node:20-slim | ~200 MB |
| node:20-alpine | ~130 MB |
| Nach Multi-Stage Build | ~50-80 MB |
Wann Docker Overkill ist
Ehrlich gesagt: Nicht jedes Startup braucht Docker von Anfang an. Hier sind Fälle, wo du ohne Docker starten kannst:
- Statische Websites: Deploy auf Vercel oder Netlify -- kein Docker nötig
- Einfache APIs: Ein Node.js-Server auf einem Hetzner VPS mit PM2 reicht oft
- Prototypen: Schnelligkeit zählt mehr als Reproduzierbarkeit
Docker lohnt sich, wenn:
- Du mehr als einen Service hast (Backend + Datenbank + Cache)
- Du im Team arbeitest und identische Entwicklungsumgebungen brauchst
- Du automatisierte Deployments aufsetzt
- Du zwischen verschiedenen Umgebungen (Dev, Staging, Prod) wechselst
Fazit
Docker ist ein mächtigtes Werkzeug, das dir als Startup-Gründer viel Kopfschmerzen ersparen kann. Es löst das "Auf meinem Rechner funktioniert es"-Problem, macht Deployments reproduzierbar und hilft dir, schneller zu iterieren.
Starte einfach: Ein Dockerfile für deine Anwendung, ein Docker Compose für die lokale Entwicklung. Den Rest -- Multi-Stage Builds, CI/CD-Integration, Production-Setup -- kannst du Schritt für Schritt hinzufügen.
Container sind heute der Standard in der Softwareentwicklung. Je früher du sie beherrschst, desto besser bist du aufgestellt -- für dein aktuelles Startup und für alles, was danach kommt.
Startup Burgenland unterstützt dich
Du willst Docker in deinem Startup einführen und brauchst Unterstützung? Bei Startup Burgenland helfen dir erfahrene Mentoren, die richtige Container-Strategie für dein Projekt zu entwickeln -- von der lokalen Entwicklungsumgebung bis zum Production-Deployment.
Weiterführende Artikel
- DevOps Grundlagen für Gründer
- Cloud-Hosting für Startups -- AWS Azure oder Hetzner
- Monitoring und Alerting einrichten
- Skalierbare Infrastruktur planen
- API-Design und Dokumentation
Dieser Beitrag ist Teil der Serie "Cloud und Infrastruktur" im Startup Burgenland Blog. In dieser Serie behandeln wir alle technischen Themen rund um Hosting, Deployment und Skalierung -- speziell für österreichische Startups und Gründer.
Über den Autor: Felix Lenhard ist Program Director und Startup Coach bei Startup Burgenland. Zuvor Managing Director beim 360 Innovation Lab, Innovation Manager bei RHI Magnesita und Serial Entrepreneur mit internationalen Exits. Über 15 Jahre Erfahrung in Innovation und Unternehmensaufbau.