Docker Compose Deployment auf dem VPS
TUTORIAL 8 min read fordnox

Docker Compose Deployment auf dem VPS

Vollständige Anleitung zur Bereitstellung von Anwendungen mit Docker Compose auf einem VPS. Von der Installation bis hin zu Multi-Container-Setups mit Netzwerken und Volumes.


Docker Compose Deployment auf dem VPS

Docker Compose ist der einfachste Weg, Anwendungen auf deinem VPS bereitzustellen und zu verwalten. Wenn du nach dem besten VPS für Docker suchst, schau dir zuerst unseren Vergleich an. Dann definierst du deinen gesamten Stack in einer einzigen Datei, deployest mit einem Befehl und aktualisierst ohne Ausfallzeiten.

Warum das wichtig ist

Herkömmliche Deployments sind mühsam:

Docker Compose löst diese Probleme:

Voraussetzungen

Schritt 1: Docker installieren

# Alte Versionen entfernen
sudo apt remove docker docker-engine docker.io containerd runc

# Abhängigkeiten installieren
sudo apt update
sudo apt install ca-certificates curl gnupg -y

# Docker-GPG-Schlüssel hinzufügen
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# Repository hinzufügen
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Docker installieren
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y

# Eigenen Benutzer zur Docker-Gruppe hinzufügen (Aus- und Einloggen erforderlich)
sudo usermod -aG docker $USER

# Installation überprüfen
docker --version
docker compose version

Melde dich ab und wieder an, damit die Gruppenänderungen wirksam werden.

Schritt 2: Projektstruktur anlegen

mkdir -p ~/apps/myapp
cd ~/apps/myapp

Empfohlene Struktur:

myapp/
├── docker-compose.yml      # Haupt-Compose-Datei
├── docker-compose.prod.yml # Produktions-Overrides
├── .env                    # Umgebungsvariablen (niemals committen!)
├── .env.example            # Vorlage für Umgebungsvariablen
├── nginx/
│   └── nginx.conf          # Benutzerdefinierte Nginx-Konfiguration
├── data/                   # Persistente Daten (in .gitignore)
└── logs/                   # Anwendungslogs (in .gitignore)

Schritt 3: Erste docker-compose.yml schreiben

Lass uns einen vollständigen Web-Stack deployen:

# docker-compose.yml
services:
  app:
    image: node:20-alpine
    working_dir: /app
    volumes:
      - ./src:/app
      - /app/node_modules
    command: npm start
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgres://user:pass@db:5432/myapp
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped
    networks:
      - internal

  db:
    image: postgres:16-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: myapp
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d myapp"]
      interval: 5s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    networks:
      - internal

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certbot/conf:/etc/letsencrypt:ro
      - ./certbot/www:/var/www/certbot:ro
    depends_on:
      - app
    restart: unless-stopped
    networks:
      - internal

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data
    command: redis-server --appendonly yes
    restart: unless-stopped
    networks:
      - internal

volumes:
  postgres_data:
  redis_data:

networks:
  internal:
    driver: bridge

Schritt 4: Umgebungsvariablen richtig verwenden

Erstelle deine .env-Datei:

# .env
DB_PASSWORD=your-super-secret-password-here
REDIS_PASSWORD=another-secret
API_KEY=your-api-key

Erstelle .env.example zur Dokumentation:

# .env.example
DB_PASSWORD=
REDIS_PASSWORD=
API_KEY=

Committe .env niemals in git! Füge sie zur .gitignore hinzu:

echo ".env" >> .gitignore
echo "data/" >> .gitignore
echo "logs/" >> .gitignore

Schritt 5: Produktions-Overrides

Erstelle eine produktionsspezifische Datei:

# docker-compose.prod.yml
services:
  app:
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 512M
        reservations:
          cpus: '0.5'
          memory: 256M
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  db:
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 1G

Deployment mit:

docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Schritt 6: Wichtige Docker Compose-Befehle

# Alle Services starten
docker compose up -d

# Logs anzeigen
docker compose logs -f

# Logs eines bestimmten Services anzeigen
docker compose logs -f app

# Alle Services stoppen
docker compose down

# Stoppen und Volumes entfernen (ACHTUNG – löscht Daten!)
docker compose down -v

# Neu bauen und starten
docker compose up -d --build

# Einen bestimmten Service neu starten
docker compose restart app

# Laufende Container anzeigen
docker compose ps

# Befehl in einem Container ausführen
docker compose exec app sh

# Ressourcennutzung anzeigen
docker stats

Schritt 7: Deployments ohne Ausfallzeiten

Für Updates ohne Downtime:

# Neue Images herunterladen
docker compose pull

# Nur geänderte Container neu erstellen
docker compose up -d --no-deps app

Oder Rolling Updates mit mehreren Replikas verwenden:

services:
  app:
    deploy:
      replicas: 2
      update_config:
        parallelism: 1
        delay: 10s

Schritt 8: Health Checks

Füge immer Health Checks hinzu:

services:
  app:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

Health-Status prüfen:

docker compose ps
docker inspect --format='{{json .State.Health}}' container_name

Schritt 9: Secrets verwalten

Für sensible Daten Docker Secrets oder externe Secret-Manager verwenden:

services:
  app:
    secrets:
      - db_password
      - api_key
    environment:
      - DB_PASSWORD_FILE=/run/secrets/db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt
  api_key:
    file: ./secrets/api_key.txt

Schritt 10: Backup-Strategie

Erstelle ein Backup-Skript:

#!/bin/bash
# backup.sh

BACKUP_DIR="/backups/$(date +%Y-%m-%d)"
mkdir -p "$BACKUP_DIR"

# PostgreSQL sichern
docker compose exec -T db pg_dump -U user myapp > "$BACKUP_DIR/db.sql"

# Volumes sichern
docker run --rm \
  -v myapp_postgres_data:/data:ro \
  -v "$BACKUP_DIR":/backup \
  alpine tar czf /backup/postgres_data.tar.gz /data

# Redis sichern
docker compose exec -T redis redis-cli BGSAVE
docker cp "$(docker compose ps -q redis)":/data/dump.rdb "$BACKUP_DIR/"

echo "Backup abgeschlossen: $BACKUP_DIR"

Praxisbeispiele

WordPress mit Datenbank

services:
  wordpress:
    image: wordpress:latest
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: ${WP_DB_PASSWORD}
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wordpress_data:/var/www/html
    depends_on:
      - db
    restart: unless-stopped

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: ${WP_DB_PASSWORD}
    volumes:
      - db_data:/var/lib/mysql
    restart: unless-stopped

volumes:
  wordpress_data:
  db_data:

Full-Stack-JavaScript-App

services:
  frontend:
    build: ./frontend
    ports:
      - "3000:3000"
    environment:
      - REACT_APP_API_URL=http://api:4000
    depends_on:
      - api

  api:
    build: ./api
    ports:
      - "4000:4000"
    environment:
      - DATABASE_URL=postgres://user:pass@db:5432/app
      - REDIS_URL=redis://redis:6379
    depends_on:
      - db
      - redis

  db:
    image: postgres:16-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: pass
      POSTGRES_USER: user
      POSTGRES_DB: app

  redis:
    image: redis:7-alpine
    volumes:
      - redisdata:/data

volumes:
  pgdata:
  redisdata:

Self-Hosted Git mit Gitea

services:
  gitea:
    image: gitea/gitea:latest
    ports:
      - "3000:3000"
      - "222:22"
    volumes:
      - gitea_data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - GITEA__database__DB_TYPE=postgres
      - GITEA__database__HOST=db:5432
      - GITEA__database__NAME=gitea
      - GITEA__database__USER=gitea
      - GITEA__database__PASSWD=${DB_PASSWORD}
    depends_on:
      - db
    restart: unless-stopped

  db:
    image: postgres:16-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: gitea
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: gitea
    restart: unless-stopped

volumes:
  gitea_data:
  postgres_data:

Best Practices

  1. Image-Versionen festlegen – Verwende postgres:16-alpine, nicht postgres:latest
  2. .env-Dateien nutzen – Halte Secrets aus Compose-Dateien heraus (siehe unsere VPS-Sicherheitsanleitung)
  3. Named Volumes für Daten – Keine Bind Mounts für Datenbanken verwenden
  4. Überall Health Checks – Wisse, wann Services wirklich bereit sind
  5. Ressourcenlimits – Verhindere, dass außer Kontrolle geratene Container deinen Server lahmlegen
  6. Logging-Limits – Setze max-size, damit die Festplatte nicht vollläuft
  7. Netzwerke verwenden – Isoliere Services, die nicht miteinander kommunizieren müssen
  8. depends_on mit Bedingungen – Warte, bis Services gesund sind, nicht nur gestartet

Häufige Fehler vermeiden

latest-Tag verwenden – Builds werden nicht mehr reproduzierbar

Daten in Containern speichern – Daten verschwinden, wenn der Container entfernt wird

.env-Dateien committen – Secrets landen für immer in der Git-History

Keine Health Checks – depends_on wartet nicht auf die Bereitschaft der App

Logs ignorieren – Ohne Limits füllen sie deine Festplatte

Datenbankports nach außen öffnen – Nur das freigeben, was externen Zugriff benötigt

Als root ausführen – USER-Direktive in Dockerfiles verwenden

Keine Restart-Policy – Container starten nach Abstürzen nicht automatisch neu

Debugging-Tipps

# Herausfinden, warum ein Container abstürzt
docker compose logs app --tail=100

# Shell in einem laufenden Container öffnen
docker compose exec app sh

# Shell in einem gestoppten Container öffnen
docker compose run app sh

# Container-Details inspizieren
docker inspect $(docker compose ps -q app)

# Netzwerkverbindung prüfen
docker compose exec app ping db

# Umgebungsvariablen anzeigen
docker compose exec app env

Häufige Fragen

Wie viel RAM brauche ich?

Für kleine Projekte reichen in der Regel 2 GB. Jeder Container hat einen gewissen Overhead – plane etwa 100 MB pro Container plus den tatsächlichen Bedarf der App ein. Hostinger VPS-Pläne beginnen bei 4 GB, was für die meisten Stacks komfortabel ausreicht.

Soll ich Docker Compose oder Kubernetes verwenden?

Docker Compose für Single-Server-Deployments (die meisten Anwendungsfälle). Kubernetes, wenn du Multi-Node-Cluster, Auto-Scaling oder ein dediziertes DevOps-Team benötigst. Nicht unnötig verkomplizieren.

Wie aktualisiere ich eine laufende Anwendung?

# Neueste Images herunterladen
docker compose pull
# Geänderte Container neu erstellen
docker compose up -d

Für eigene Builds: docker compose up -d --build

Kann ich Docker Compose mit dem Nginx Proxy Manager verwenden?

Ja! Ports nicht direkt freigeben – einfach die Container ins gleiche Netzwerk wie NPM legen. Siehe unsere Reverse-Proxy-Anleitung.

Wie persistiere ich Daten?

Named Volumes verwenden (Docker verwaltet den Speicherort) oder Bind Mounts (du gibst den Pfad an). Named Volumes sind für Datenbanken empfohlen.

Was ist der Unterschied zwischen up und start?

up erstellt und startet Container. start startet nur bereits vorhandene, gestoppte Container. Immer up -d verwenden.


Nächste Schritte: Richte automatische Backups ein, um deine Docker-Daten zu schützen, und füge Monitoring hinzu, um den Zustand deiner Container im Blick zu behalten.

~/docker-compose-vps-guide/get-started

Ready to get started?

Get the best VPS hosting deal today. Hostinger offers 4GB RAM VPS starting at just $4.99/mo.

Get Hostinger VPS — $4.99/mo

// up to 75% off + free domain included

// related topics

Docker Compose VPS Docker Container-Deployment Docker-Tutorial VPS-Deployment

// related guides

Andrius Putna

Andrius Putna

I am Andrius Putna. Geek. Since early 2000 in love tinkering with web technologies. Now AI. Bridging business and technology to drive meaningful impact. Combining expertise in customer experience, technology, and business strategy to deliver valuable insights. Father, open-source contributor, investor, 2xIronman, MBA graduate.

// last updated: February 6, 2026. Disclosure: This article may contain affiliate links.