Zum Inhalt springen

Docker und Container für Einsteiger

Felix Lenhard 13 min
Zurück zum Blog

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.

EigenschaftVMContainer
StartzeitMinutenSekunden
GrösseGigabytesMegabytes
IsolationVollständigProzess-Level
RessourcenverbrauchHochNiedrig
PortabilitätBegrenztSehr 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:

  1. Liest das Dockerfile
  2. Lädt das Base Image herunter
  3. Führt die Anweisungen aus
  4. 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

  1. Nutze einen nicht-root User: USER node im Dockerfile
  2. Verwende spezifische Image-Tags: node:20.11-alpine statt node:latest
  3. Scanne Images auf Schwachstellen: docker scout quickview mein-startup-app
  4. 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:

ImageGrö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.

Jetzt Beratung anfragen

Weiterführende Artikel


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.

Erstgespräch vereinbaren

Du überlegst zu gründen oder bist schon mittendrin? Schreib uns ein formloses E-Mail -- wir melden uns innerhalb weniger Tage.

E-Mail schreiben