docs(deploy): schlankes Compose-Beispiel im Wurzelverzeichnis

Das Compose in deploy/ war zu über der Hälfte Kommentar (73 von 137
Zeilen) und mischte zwei Zwecke. Jetzt getrennt:

  Wurzel   docker-compose.example.yml + .env.example  – betreiben,
           zieht das fertige Abbild, braucht keinen Quelltext
  deploy/  docker-compose.yml + .env.example          – entwickeln,
           baut aus dem Quelltext, kennt alle Schalter

Die schlanke .env.example führt nur die Pflichtwerte und Office; die
vollständige Liste mit Erklärungen bleibt in deploy/.env.example.
Die kopierte /docker-compose.yml ist ignoriert – sie gehört in die
Umgebung, nicht ins Repo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Opus 5 committed 2026-08-24 09:33:20 +02:00
1 parent 62e80dd2f5
commit 13681958dd
5 files changed
+228 -105

No files matched your search

+33 -82
View File
@@ -1,59 +1,40 @@
# Aether – Entwicklung: aus dem Quelltext bauen.
#
# docker compose -f deploy/docker-compose.yml build app
# docker compose -f deploy/docker-compose.yml up -d
#
# Zum **Betreiben** ist docker-compose.example.yml im Wurzelverzeichnis
# gedacht: schlanker und ohne Quelltext, es zieht das fertige Abbild.
#
# Erklärungen zu allem hier: README.md
services:
app:
container_name: aether-app
# Fertiges Abbild aus der eigenen Gitea-Registry.
#
# Ein Tag mit Datum **und** `latest`: Zum Aufbauen nimmt man `latest`, zum
# Zurückrollen den datierten. Wer nur `latest` fährt, hat im Fehlerfall
# nichts, worauf er zurück kann – der Name zeigt dann schon auf die
# Fassung, die gerade Ärger macht.
#
# Anmelden vor dem ersten Zug:
# docker login gitea.honeyy.duckdns.org
#
# Aktualisieren:
# docker compose pull app && docker compose up -d app
build:
# Kontext ist das Wurzelverzeichnis – das Abbild braucht backend/ und
# frontend/, nicht nur diesen Ordner.
context: ..
dockerfile: deploy/Dockerfile
image: gitea.honeyy.duckdns.org/docker/aether:latest
# Selbst bauen statt ziehen? Dann die Zeile darüber auskommentieren und
# diese drei einkommentieren. Der Kontext ist das **Wurzelverzeichnis** des
# Projekts, nicht dieser Ordner – das Abbild braucht backend/ und frontend/.
#
# docker compose build app
#
# build:
# context: ..
# dockerfile: deploy/Dockerfile
restart: unless-stopped
env_file: .env
# Als der Nutzer laufen, dem das Datenverzeichnis auf dem Wirt gehört.
# Ohne das legt der Container seine Dateien als uid 100 an, und du kämst
# per SFTP nur als root daran.
user: "${APP_UID:-1000}:${APP_GID:-1000}"
# ocrmypdf und ghostscript legen Arbeitsdateien unter $HOME ab. Bei einem
# abweichenden Nutzer ist das Heimverzeichnis aus dem Image nicht mehr
# beschreibbar – /tmp im Container ist es immer.
environment:
# ocrmypdf legt Arbeitsdateien unter $HOME ab – das aus dem Image ist
# für einen abweichenden Nutzer nicht beschreibbar.
HOME: /tmp
depends_on:
postgres:
condition: service_healthy
volumes:
# Bind-Mount statt eines benannten Volumes: Deine Dateien liegen damit in
# einem gewöhnlichen Verzeichnis auf dem Wirt, mit echten Ordnern und
# echten Namen (users/<name>/home/…). Du kommst per SFTP, rsync oder
# einfach im Dateimanager daran – auch dann noch, wenn diese Anwendung
# nie wieder startet. Bei einem benannten Volume läge alles unter
# /var/lib/docker/volumes/ und gehörte root.
#
# Nur lesen. Wer hier hineinschreibt, hat eine Datei auf der Platte, die
# in der Datenbank nicht existiert – die Cloud sieht sie nicht, und der
# GC räumt sie irgendwann weg. Zum Hineinlegen ist WebDAV der Weg.
# Bind-Mount, kein benanntes Volume: echte Ordner auf dem Wirt, per SFTP
# erreichbar. Nur lesend anfassen – zum Hineinlegen ist WebDAV der Weg,
# sonst kennt die Datenbank die Datei nicht.
- ${DATA_PATH:-./data}:/data
networks:
- internal
networks: [internal]
ports:
# Expose on loopback only — your external reverse proxy connects here.
# Host port via APP_PORT in .env (default 8080), container port stays 8080.
# Nur loopback. TLS und Domain gehören auf den Reverse Proxy des Wirts.
- "127.0.0.1:${APP_PORT:-8080}:8080"
postgres:
@@ -66,71 +47,41 @@ services:
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- internal
networks: [internal]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
interval: 5s
timeout: 5s
retries: 10
# ── Office (optional) ──────────────────────────────────────────────────────
# Laeuft nur mit `--profile office`. Ohne das Profil startet der Container
# nicht – wer kein Office will, soll nicht 1 GB Arbeitsspeicher dafuer
# abgeben, nur weil die Zeilen hier stehen.
#
# docker compose --profile office up -d
#
# Der Office-Server holt die Datei **selbst** beim Backend ab. Er muss Aether
# also unter PUBLIC_URL erreichen, nicht unter localhost. Im selben
# Compose-Netz erreicht er es unter `app:8080` – dafuer steht `extra_hosts`
# bzw. ein PUBLIC_URL, das auf den Wirt zeigt.
# Office – nur mit `--profile office`, kostet rund 1 GB Arbeitsspeicher.
collabora:
container_name: aether-collabora
image: collabora/code:latest
profiles: ["office"]
restart: unless-stopped
environment:
# Welche Hosts ihre Dateien hier oeffnen duerfen. Punkte maskiert, weil
# Collabora den Wert als regulaeren Ausdruck liest.
aliasgroup1: ${OFFICE_ALIASGROUP:-http://host.docker.internal:8090}
# Zwei getrennte Schalter, und beide zaehlen:
#
# ssl.enable – spricht Collabora selbst HTTPS?
# ssl.termination – endet HTTPS davor, an einem Reverse Proxy?
#
# Steht termination auf true, sagt Collabora dem Browser „verbinde dich
# per wss://" – auch dann, wenn davor gar kein HTTPS liegt. Im reinen
# HTTP-Testaufbau scheitert damit die WebSocket-Verbindung, und der
# Editor bleibt leer stehen, obwohl alles andere laedt (gemessen).
#
# Produktiv hinter deinem Reverse Proxy: OFFICE_SSL_TERMINATION=true.
#
# mount_jail_tree=false: Collabora sperrt jedes Dokument sonst in einen
# eigenen Einhaengepunkt und braucht dafuer CAP_SYS_ADMIN. Die Rechte
# dem Container zu geben waere der teurere Weg – ohne den Schalter
# scheitert der Start eines Dokuments mit „Failed to exec coolmount".
aliasgroup1: ${OFFICE_ALIASGROUP:-http://app:8080}
# ssl.termination=true nur, wenn davor wirklich HTTPS endet – sonst
# verlangt Collabora eine wss://-Verbindung, die es nicht gibt, und der
# Editor bleibt leer. mount_jail_tree=false spart CAP_SYS_ADMIN.
extra_params: >-
--o:ssl.enable=${OFFICE_SSL:-false}
--o:ssl.termination=${OFFICE_SSL_TERMINATION:-false}
--o:mount_jail_tree=false
dictionaries: de_DE en_US
cap_add:
- MKNOD
cap_add: [MKNOD]
extra_hosts:
# Damit der Container den Wirt erreicht – dort haengt der Reverse Proxy
# bzw. der veroeffentlichte Port von Aether.
- "host.docker.internal:host-gateway"
networks:
- internal
networks: [internal]
ports:
- "127.0.0.1:${OFFICE_PORT:-9980}:9980"
volumes:
postgres_data:
# "data" ist bewusst weg – die Dateien liegen jetzt unter DATA_PATH auf dem
# Wirt (siehe oben). Die Datenbank bleibt ein benanntes Volume: An ihre
# Dateien geht man nicht von Hand, und Postgres mag fremde Besitzer nicht.
# "data" ist bewusst weg – die Dateien liegen unter DATA_PATH auf dem Wirt.
# Die Datenbank bleibt ein benanntes Volume: An ihre Dateien geht man nicht
# von Hand, und Postgres mag fremde Besitzer nicht.
networks:
internal: