Docker/Podman Composer — Anwendungsbeispiele

Praxisnahe Durchläufe: nginx und Postgres nach Compose, mehrere Befehle samt Netz, Dollarzeichen, Compose als Startskript mit build und depends_on, podman.

Zurück zur Übersicht: Docker/Podman Composer · Tool live öffnen: www.jpkc.com/tools/docker/

Das Manual erklärt beide Richtungen und alle unterstützten Flags im Detail. Diese Seite zeigt konkrete Konvertierungen mit Eingabe und tatsächlichem Ergebnis. Das Tool wandelt live um — du fügst die Eingabe ein, das Ergebnis steht nach einem Augenblick darunter. Die Oberfläche ist auf Englisch, Tab- und Button-Namen stehen deshalb im Original. Wenn nicht anders angegeben, ist das Feld Compose project name leer.

Beispiel 1: Einfacher nginx-Container (Run → Compose)

Der Klassiker — ein kurzer Run-Befehl wird zur Compose-Datei. Eingabe im Tab Run → Compose:

docker run -d --name web -p 8080:80 nginx:alpine

Ergebnis im Feld Docker Compose YAML:

yaml
services:
  web:
    image: nginx:alpine
    container_name: web
    ports:
      - "8080:80"

Beachte: Das -d (detach) verschwindet — in Compose ist das implizit. Der Service-Name web kommt aus --name; ohne --name hätte das Tool ihn aus dem Image abgeleitet (nginx). Die Port-Angabe steht in Anführungszeichen, weil ältere YAML-Leser 8080:80 sonst als Zahl deuten könnten.

Beispiel 2: Volumes, Ports, Environment und Healthcheck

Ein realistischer Datenbank-Container mit mehreren Flag-Typen. Eingabe:

Terminal
docker run -d \
  --name db \
  -e POSTGRES_PASSWORD=secret \
  -v pgdata:/var/lib/postgresql/data \
  -p 5432:5432 \
  --restart unless-stopped \
  --health-cmd "pg_isready -U postgres" \
  --health-interval 10s \
  --memory 512m \
  postgres:16

Ergebnis:

yaml
services:
  db:
    image: postgres:16
    container_name: db
    restart: unless-stopped
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD=secret
    healthcheck:
      test:
        - CMD-SHELL
        - pg_isready -U postgres
      interval: 10s
    mem_limit: 512m

volumes:
  pgdata:
    external: true

Drei Dinge lohnen die Aufmerksamkeit: Der --health-cmd wird zu einer test-Liste in der CMD-SHELL-Form, und --memory landet als mem_limit. Der per Anführungszeichen geklammerte Healthcheck-Befehl bleibt dank Tokenizer als ein Argument erhalten. Und das benannte Volume pgdata deklariert das Tool unten als external: true — Compose nutzt so das Volume, das dein docker run schon angelegt hat, statt ein neues <projekt>_pgdata zu erzeugen.

Beispiel 3: Multi-Flag-Befehl mit kombinierten Kurz-Flags

Hier kommen kombinierte Flags und ein nachgestelltes Kommando ins Spiel. Eingabe:

docker run -it --rm -p 3000:3000 node:20 bash

Ergebnis:

yaml
services:
  node:
    image: node:20
    command:
      - bash
    ports:
      - "3000:3000"
    stdin_open: true
    tty: true

Unter der Ausgabe erscheint dazu ein gelber Hinweis:

--rm: Compose keeps stopped containers; use "docker compose run --rm" for one-off runs.

Was hier passiert: -it wird in -i und -t zerlegt und ergibt stdin_open: true und tty: true. Das --rm fällt weg, weil es keine Compose-Entsprechung hat — der Hinweis nennt dir die Alternative. Das bash nach dem Image-Namen wird zum command, als Liste mit einem Wort pro Eintrag. Da kein --name gesetzt ist, leitet das Tool den Service-Namen aus dem Image ab: node.

Beispiel 4: Voller Funktionsumfang (der Example-Knopf)

Der Example-Knopf im Run-Tab lädt ein kleines Setup aus drei Befehlen: ein Netz, einen nginx-Webserver und eine Node-App, die sich das Netz teilen:

Terminal
# Network shared by both containers
docker network create mynet

docker run -d \
  --name webserver \
  -p 8080:80 \
  -v /var/www:/usr/share/nginx/html:ro \
  -v nginx-logs:/var/log/nginx \
  -e NGINX_HOST=example.com \
  --restart unless-stopped \
  --network mynet \
  --label app=webserver \
  --memory 256m \
  --health-cmd "curl -fs http://localhost/ || exit 1" \
  --health-interval 30s \
  --log-opt max-size=10m \
  nginx:1.27-alpine

docker run -d --name app --network mynet -w /app -v "$PWD/app:/app" \
  -e NODE_ENV=production -e "GREETING=Hello, world" \
  node:22-alpine node server.js

Ergebnis:

yaml
services:
  webserver:
    image: nginx:1.27-alpine
    container_name: webserver
    restart: unless-stopped
    ports:
      - "8080:80"
    volumes:
      - /var/www:/usr/share/nginx/html:ro
      - nginx-logs:/var/log/nginx
    environment:
      - NGINX_HOST=example.com
    networks:
      - mynet
    healthcheck:
      test:
        - CMD-SHELL
        - curl -fs http://localhost/ || exit 1
      interval: 30s
    logging:
      options:
        max-size: 10m
    labels:
      - app=webserver
    mem_limit: 256m
  app:
    image: node:22-alpine
    container_name: app
    command:
      - node
      - server.js
    volumes:
      - $PWD/app:/app
    environment:
      - NODE_ENV=production
      - GREETING=Hello, world
    networks:
      - mynet
    working_dir: /app

networks:
  mynet:
    name: mynet

volumes:
  nginx-logs:
    external: true

Die zwei run-Befehle werden zu zwei Services in einer Datei; die Kommentarzeile überspringt das Tool. Weil docker network create mynet in der Eingabe steht, deklariert das Tool das Netz mit name: mynet — Compose verwendet damit genau dieses Netz statt eines neuen <projekt>_mynet. Das Volume nginx-logs wurde nirgends angelegt und gilt deshalb als external. Das || im Healthcheck bleibt erhalten, weil es in Anführungszeichen steht. $PWD bleibt eine Variable, die Compose beim Start einsetzt.

Beispiel 5: Die Gegenrichtung — Compose → Run mit mehreren Services

Wechsle in den Tab Compose → Run, trag im Feld Compose project name shop ein und füge eine Compose-Datei mit zwei Services ein:

yaml
services:
  web:
    image: nginx:alpine
    container_name: web
    ports:
      - "8080:80"
    restart: unless-stopped
    networks:
      - mynet
    depends_on:
      - app
  app:
    image: node:20-alpine
    container_name: app
    restart: always
    working_dir: /app
    volumes:
      - ./app:/app
    environment:
      NODE_ENV: production
      PORT: "3000"
    networks:
      - mynet

networks:
  mynet:

Mit Runtime docker entsteht dieses Skript:

Terminal
# Networks and volumes
docker network inspect shop_mynet >/dev/null 2>&1 || docker network create shop_mynet

# app
docker run -d \
  --name app \
  --restart always \
  --workdir /app \
  --network shop_mynet \
  --network-alias app \
  -v ./app:/app \
  -e NODE_ENV=production \
  -e PORT=3000 \
  node:20-alpine

# web
docker run -d \
  --name web \
  --restart unless-stopped \
  --network shop_mynet \
  --network-alias web \
  -p 8080:80 \
  nginx:alpine

Mehrere Dinge fallen auf: Zuerst legt das Skript das Netz an — docker run würde das nicht tun —, und zwar nur, wenn es noch nicht existiert. Es heißt wie bei Compose shop_mynet. Weil web per depends_on auf app wartet, startet app zuerst. Jeder Container bekommt seinen Service-Namen als --network-alias, sodass web die App wie unter Compose als app erreicht. Das environment-Objekt wird zu einzelnen -e KEY=VALUE-Flags aufgelöst. Ein gelber Hinweis erinnert daran, dass der relative Pfad ./app vom aktuellen Verzeichnis abhängt — führ das Skript neben der Compose-Datei aus. Über run.sh lädst du das Ganze als lauffähiges Skript (mit #!/bin/sh und set -e) herunter — starte es mit sh run.sh oder nach chmod +x run.sh direkt.

Beispiel 6: Befehle für Podman erzeugen

Dieselbe Compose-Eingabe, aber im Runtime-Schalter podman gewählt. Aus dem Netz und dem web-Service wird dann:

Terminal
# Networks and volumes
podman network inspect shop_mynet >/dev/null 2>&1 || podman network create shop_mynet

# web
podman run -d \
  --name web \
  --restart unless-stopped \
  --network shop_mynet \
  --network-alias web \
  -p 8080:80 \
  nginx:alpine

Der Unterschied ist das Befehlswort podman statt docker, auch für die Netzbefehle — die Flags bleiben gleich, weil Podman die docker run-Optionen kompatibel nachbildet. Nur GPU-Reservierungen schreibt das Tool für Podman anders (--device nvidia.com/gpu=all). Praktisch, wenn du eine vorhandene Compose-Datei auf einem Podman-Host von Hand testen willst. Die Runtime-Wahl merkt sich das Tool für deinen nächsten Besuch.

Beispiel 7: Dollarzeichen und Variablen

Ein $ bedeutet in der Shell etwas anderes als in Compose. Eingabe im Tab Run → Compose:

docker run -d --name shop -e 'PRICE=$5' -e "HOME_DIR=$HOME" -v "$PWD/data:/data" \
  --network host myorg/shop:1.2

Ergebnis:

yaml
services:
  shop:
    image: myorg/shop:1.2
    container_name: shop
    volumes:
      - $PWD/data:/data
    environment:
      - PRICE=$$5
      - HOME_DIR=$HOME
    network_mode: host

'$5' steht in einfachen Anführungszeichen, ist also ein wörtliches Dollarzeichen — in Compose wird daraus $$5. $HOME und $PWD sind Variablen und bleiben es; Compose setzt sie beim Start ein. --network host ist kein Netz, das Compose verwalten könnte, und wird deshalb zu network_mode: host.

Die Gegenrichtung zeigt, wie das Tool solche Werte wieder shell-sicher macht. Projektname shop, Eingabe im Tab Compose → Run:

yaml
services:
  shop:
    image: myorg/shop:1.2
    environment:
      - PRICE=$$5
      - HOME_DIR=$HOME
      - GREETING=${GREETING:-Hello}
    volumes:
      - $PWD/data:/data
    network_mode: host

Ergebnis:

Terminal
# shop
docker run -d \
  --name shop-shop-1 \
  --network host \
  -v "$PWD"/data:/data \
  -e 'PRICE=$5' \
  -e HOME_DIR="$HOME" \
  -e GREETING="${GREETING:-Hello}" \
  myorg/shop:1.2

$$5 wird wieder zum wörtlichen $5 in einfachen Anführungszeichen, die Variablen landen in doppelten Anführungszeichen und expandieren in der Shell — samt Vorgabewert. Ohne container_name heißt der Container wie bei Compose shop-shop-1.

Beispiel 8: Compose-Datei mit build, depends_on und zwei Netzen

Der Example-Knopf im Tab Compose → Run lädt das Projekt shop mit allem, was die Gegenrichtung interessant macht — name: shop steht in der Datei, das Projektfeld kann leer bleiben:

yaml
name: shop

services:
  webserver:
    image: nginx:1.27-alpine
    restart: unless-stopped
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html:ro
      - nginx-logs:/var/log/nginx
    environment:
      NGINX_HOST: ${HOST:-example.com}
    networks:
      - front
      - back
    healthcheck:
      test: ["CMD-SHELL", "wget -qO- http://localhost/ || exit 1"]
      interval: 30s
      retries: 3
    logging:
      driver: json-file
      options:
        max-size: 10m
    depends_on:
      app:
        condition: service_healthy

  app:
    build: ./app
    working_dir: /app
    command: node server.js
    environment:
      NODE_ENV: production
      PRICE_TAG: "costs $$5"
    networks:
      back:
        aliases: [api]
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: "0.5"
    stop_grace_period: 1m30s

networks:
  front:
  back:
    internal: true

volumes:
  nginx-logs:

Ergebnis (Runtime docker):

Terminal
# Networks and volumes
docker network inspect shop_back >/dev/null 2>&1 || docker network create --internal shop_back
docker network inspect shop_front >/dev/null 2>&1 || docker network create shop_front

# app
docker build -t shop-app ./app
docker run -d \
  --name shop-app-1 \
  --workdir /app \
  --memory 512M \
  --cpus 0.5 \
  --network shop_back \
  --network-alias app \
  --network-alias api \
  -e NODE_ENV=production \
  -e 'PRICE_TAG=costs $5' \
  --stop-timeout 90 \
  shop-app node server.js

# webserver
docker run -d \
  --name shop-webserver-1 \
  --restart unless-stopped \
  --network shop_front \
  --network-alias webserver \
  -p 8080:80 \
  -v ./html:/usr/share/nginx/html:ro \
  -v shop_nginx-logs:/var/log/nginx \
  -e NGINX_HOST="${HOST:-example.com}" \
  --log-driver json-file \
  --log-opt max-size=10m \
  --health-cmd 'wget -qO- http://localhost/ || exit 1' \
  --health-interval 30s \
  --health-retries 3 \
  nginx:1.27-alpine
docker network connect --alias webserver shop_back shop-webserver-1

Das Skript zeigt fast alle Kniffe der Gegenrichtung: build: ./app wird zu einem docker build -t shop-app vor dem run. Das interne Netz back entsteht mit --internal. deploy.resources.limits landet als --memory und --cpus, 1m30s als --stop-timeout 90, und command: node server.js steht nach dem Image. $$5 wird zum wörtlichen $5 in einfachen Anführungszeichen, ${HOST:-example.com} zur Shell-Variable mit Vorgabewert. Der webserver hängt per --network am ersten Netz und per docker network connect am zweiten; das benannte Volume heißt wie bei Compose shop_nginx-logs.

Zwei gelbe Hinweise gehören dazu: Der relative Pfad ./html hängt vom aktuellen Verzeichnis ab, und die Bedingung service_healthy prüft das Skript nicht — es startet die Container nur in der richtigen Reihenfolge.


Noch tiefer: das Manual für jedes Flag, die Tipps & Tricks für die Grenzen und Kniffe. Ausprobieren kannst du alles direkt im Tool.