fix(docker): keine Ports auf 0.0.0.0 und ein Overlay ohne Freigaben
CI / pruefen (push) Has been cancelled
CI / pruefen (push) Has been cancelled
Der Datenbankport wurde in beiden Betriebsarten auf 0.0.0.0 gelegt, obwohl ihn nur die Entwicklung braucht: im Container spricht die Anwendung "db:5432" ueber das Compose-Netz an, und backup.sh geht ueber docker exec. Auf einem Server lauschte Postgres damit im Netz, geschuetzt allein durch das Passwort aus der .env. Die Anleitung riet zwar zu 127.0.0.1, aber als Handgriff statt als Voreinstellung -- die falsche Richtung. Datenbank und Adminer binden jetzt von Haus aus auf 127.0.0.1. Liegen mehrere Stacks auf einem Host und regelt ein gemeinsamer Reverse Proxy den Zugang, braucht Nebula gar keinen Host-Port. docker-compose.server.yml nimmt beide Freigaben zurueck und haengt die Anwendung in ein geteiltes Netz. Das braucht !reset: Compose fuehrt "ports" beim Zusammenfuehren anhaengend statt ersetzend zusammen, eine leere Liste im Override wuerde also nichts loeschen. update.sh las die Adresse aus APP_PORT und haette in diesem Modus einen Port gemeldet, den es nicht gibt. Es fragt jetzt Docker und sagt sonst, dass nur das interne Netz erreichbar ist. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+16
-3
@@ -38,10 +38,23 @@ DATABASE_URL="postgresql://nebula:nebula@localhost:5432/nebula?schema=public"
|
|||||||
POSTGRES_USER="nebula"
|
POSTGRES_USER="nebula"
|
||||||
POSTGRES_PASSWORD="nebula"
|
POSTGRES_PASSWORD="nebula"
|
||||||
POSTGRES_DB="nebula"
|
POSTGRES_DB="nebula"
|
||||||
# Host-Port, unter dem die Datenbank erreichbar ist
|
# Host-Port, unter dem die Datenbank erreichbar ist. Nur die Entwicklung
|
||||||
POSTGRES_PORT="5432"
|
# braucht ihn: im Container spricht die Anwendung "db:5432" an. Deshalb an
|
||||||
|
# 127.0.0.1 gebunden, damit er nicht im Netz steht.
|
||||||
|
POSTGRES_PORT="127.0.0.1:5432"
|
||||||
# Host-Port fuer Adminer (--profile tools)
|
# Host-Port fuer Adminer (--profile tools)
|
||||||
ADMINER_PORT="8080"
|
ADMINER_PORT="127.0.0.1:8080"
|
||||||
|
|
||||||
|
# ── Betrieb ohne veroeffentlichte Ports ──────────────────────
|
||||||
|
# Liegen mehrere Stacks auf demselben Docker-Host und regelt ein gemeinsamer
|
||||||
|
# Reverse Proxy den Zugang, veroeffentlicht Nebula gar keinen Port. Dann diese
|
||||||
|
# beiden Zeilen aktivieren – danach genuegt ueberall "docker compose up -d":
|
||||||
|
# COMPOSE_FILE="docker-compose.yml:docker-compose.server.yml"
|
||||||
|
# COMPOSE_PROFILES="full"
|
||||||
|
#
|
||||||
|
# Name des geteilten Netzes und der Alias, unter dem der Proxy Nebula findet.
|
||||||
|
# PROXY_NETWORK="proxy"
|
||||||
|
# PROXY_ALIAS="nebula"
|
||||||
|
|
||||||
# ── Sicherheit ───────────────────────────────────────────────
|
# ── Sicherheit ───────────────────────────────────────────────
|
||||||
# Schluessel zur Verschluesselung der Git-Access-Tokens (AES-256-GCM).
|
# Schluessel zur Verschluesselung der Git-Access-Tokens (AES-256-GCM).
|
||||||
|
|||||||
+68
-10
@@ -86,19 +86,54 @@ APP_PORT=127.0.0.1:3000
|
|||||||
> **`APP_URL` später zu ändern** macht bestehende **Passkeys ungültig**, weil
|
> **`APP_URL` später zu ändern** macht bestehende **Passkeys ungültig**, weil
|
||||||
> sie an die rpID gebunden sind. Passwörter funktionieren weiter.
|
> sie an die rpID gebunden sind. Passwörter funktionieren weiter.
|
||||||
|
|
||||||
### Datenbankport schließen
|
### Welche Ports überhaupt hinausgehen
|
||||||
|
|
||||||
`docker-compose.yml` bildet Postgres standardmäßig auf `5432` ab, damit man
|
| Container | Host-Port | Wann | Wird gebraucht? |
|
||||||
in der Entwicklung von außen drankommt. Auf einem Server hat die Datenbank
|
| --- | --- | --- | --- |
|
||||||
nichts im Internet zu suchen:
|
| `nebula-app` | `APP_PORT` → 3000 | `--profile full` | ja — das ist die Anwendung |
|
||||||
|
| `nebula-db` | `POSTGRES_PORT` → 5432 | immer | **nein** |
|
||||||
|
| `nebula-adminer` | `ADMINER_PORT` → 8080 | nur `--profile tools` | nein |
|
||||||
|
|
||||||
```env
|
**Der Datenbankport muss nicht nach außen.** Im Vollbetrieb spricht die
|
||||||
POSTGRES_PORT=127.0.0.1:5432
|
Anwendung Postgres über das interne Docker-Netz als `db:5432` an — die
|
||||||
|
`DATABASE_URL` setzt Compose selbst. Auch `scripts/backup.sh` und
|
||||||
|
`restore.sh` gehen über `docker exec`, nicht über den Host. Die Freigabe
|
||||||
|
existiert allein für die Entwicklung, wo `npm run dev` auf dem Host läuft.
|
||||||
|
|
||||||
|
Sie ist deshalb auf `127.0.0.1` gebunden und damit vom Netz aus nicht
|
||||||
|
erreichbar. Auf einem Server kann sie ganz weg — siehe den nächsten
|
||||||
|
Abschnitt.
|
||||||
|
|
||||||
|
### Variante: gar keinen Port veröffentlichen
|
||||||
|
|
||||||
|
Liegen mehrere Stacks auf demselben Docker-Host und regelt ein gemeinsamer
|
||||||
|
Reverse Proxy den Zugang, braucht Nebula überhaupt keinen Host-Port. Dafür
|
||||||
|
gibt es **`docker-compose.server.yml`**: es nimmt beide Freigaben zurück und
|
||||||
|
hängt die Anwendung zusätzlich in ein geteiltes Netz.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker network create proxy # falls der Proxy-Stack es nicht schon anlegt
|
||||||
```
|
```
|
||||||
|
|
||||||
Noch besser: die `ports:`-Zeile des `db`-Dienstes ganz entfernen. Die
|
```env
|
||||||
Anwendung erreicht die Datenbank über das interne Docker-Netz, nicht über
|
# .env
|
||||||
den Host.
|
COMPOSE_FILE="docker-compose.yml:docker-compose.server.yml"
|
||||||
|
COMPOSE_PROFILES="full"
|
||||||
|
PROXY_NETWORK="proxy" # so heißt das Netz
|
||||||
|
PROXY_ALIAS="nebula" # so findet der Proxy die Anwendung
|
||||||
|
```
|
||||||
|
|
||||||
|
Compose liest beides aus der `.env`. Danach genügt überall — auch in
|
||||||
|
`scripts/update.sh` — ein schlichtes `docker compose up -d`. Im Proxy zeigt
|
||||||
|
das Ziel dann nicht mehr auf `127.0.0.1:3000`, sondern auf **`nebula:3000`**;
|
||||||
|
Schritt 6 zeigt beide Schreibweisen.
|
||||||
|
|
||||||
|
> Ohne die Datei würde ein Override die Portlisten **zusammenhängen** statt
|
||||||
|
> sie zu ersetzen — Compose führt `ports` anfügend zusammen. Das `!reset`
|
||||||
|
> darin ist genau dafür da und braucht Compose 2.24 oder neuer.
|
||||||
|
>
|
||||||
|
> Das Trennzeichen in `COMPOSE_FILE` ist unter Linux der Doppelpunkt, unter
|
||||||
|
> Windows das Semikolon.
|
||||||
|
|
||||||
## 4 · Starten
|
## 4 · Starten
|
||||||
|
|
||||||
@@ -114,6 +149,13 @@ docker compose --profile full logs -f app # mitlesen, Strg+C beendet nur das
|
|||||||
curl -s localhost:3000/api/health # {"status":"ok","database":"up",…}
|
curl -s localhost:3000/api/health # {"status":"ok","database":"up",…}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Ohne veröffentlichten Port geht die Frage an den Container statt an den Host
|
||||||
|
— das Abbild bringt einen `HEALTHCHECK` mit:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker inspect --format '{{.State.Health.Status}}' nebula-app # healthy
|
||||||
|
```
|
||||||
|
|
||||||
## 5 · Ersten Zugang anlegen
|
## 5 · Ersten Zugang anlegen
|
||||||
|
|
||||||
Ruf `https://nebula.deine-domain.de/setup` auf (nach Schritt 6) oder
|
Ruf `https://nebula.deine-domain.de/setup` auf (nach Schritt 6) oder
|
||||||
@@ -151,6 +193,16 @@ sudo systemctl reload caddy
|
|||||||
Das genügt. Caddy setzt `X-Forwarded-*` von selbst, holt das Zertifikat bei
|
Das genügt. Caddy setzt `X-Forwarded-*` von selbst, holt das Zertifikat bei
|
||||||
Let's Encrypt und leitet `http` auf `https` um.
|
Let's Encrypt und leitet `http` auf `https` um.
|
||||||
|
|
||||||
|
Läuft der Proxy selbst als Container im geteilten Netz (die Variante ohne
|
||||||
|
veröffentlichte Ports), lautet das Ziel stattdessen `nebula:3000` —
|
||||||
|
Docker löst den Alias auf:
|
||||||
|
|
||||||
|
```caddyfile
|
||||||
|
nebula.deine-domain.de {
|
||||||
|
reverse_proxy nebula:3000
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Dasselbe mit nginx</summary>
|
<summary>Dasselbe mit nginx</summary>
|
||||||
|
|
||||||
@@ -189,7 +241,13 @@ sudo ufw enable
|
|||||||
```
|
```
|
||||||
|
|
||||||
Port 3000 und 5432 bleiben **zu**. Beide sind nach Schritt 3 ohnehin nur an
|
Port 3000 und 5432 bleiben **zu**. Beide sind nach Schritt 3 ohnehin nur an
|
||||||
`127.0.0.1` gebunden; die Firewall ist der zweite Riegel.
|
`127.0.0.1` gebunden — oder gar nicht erst veröffentlicht; die Firewall ist
|
||||||
|
der zweite Riegel.
|
||||||
|
|
||||||
|
> Docker schreibt seine Portfreigaben direkt in `iptables` und geht dabei an
|
||||||
|
> `ufw` **vorbei**. Eine Freigabe auf `0.0.0.0` steht also im Netz, auch wenn
|
||||||
|
> `ufw` sie zu melden scheint. Genau deshalb wird hier an `127.0.0.1`
|
||||||
|
> gebunden, statt sich auf die Firewall zu verlassen.
|
||||||
|
|
||||||
## 8 · Sicherungen automatisieren
|
## 8 · Sicherungen automatisieren
|
||||||
|
|
||||||
|
|||||||
@@ -122,8 +122,8 @@ Alle Variablen sind in [`.env.example`](.env.example) dokumentiert.
|
|||||||
| `PORT` | Port der Anwendung | `3000` |
|
| `PORT` | Port der Anwendung | `3000` |
|
||||||
| `APP_PORT` | Host-Port-Mapping des App-Containers | `3000` |
|
| `APP_PORT` | Host-Port-Mapping des App-Containers | `3000` |
|
||||||
| `POSTGRES_USER` / `POSTGRES_PASSWORD` / `POSTGRES_DB` | Zugangsdaten des DB-Containers | `nebula` |
|
| `POSTGRES_USER` / `POSTGRES_PASSWORD` / `POSTGRES_DB` | Zugangsdaten des DB-Containers | `nebula` |
|
||||||
| `POSTGRES_PORT` | Host-Port der Datenbank | `5432` |
|
| `POSTGRES_PORT` | Host-Port der Datenbank — nur für die Entwicklung, im Container läuft es über `db:5432` | `127.0.0.1:5432` |
|
||||||
| `ADMINER_PORT` | Host-Port von Adminer (`--profile tools`) | `8080` |
|
| `ADMINER_PORT` | Host-Port von Adminer (`--profile tools`) | `127.0.0.1:8080` |
|
||||||
| `GIT_API_TIMEOUT_MS` | Timeout für Git-API-Aufrufe | `15000` |
|
| `GIT_API_TIMEOUT_MS` | Timeout für Git-API-Aufrufe | `15000` |
|
||||||
| `GIT_ALLOW_LOOPBACK` | Git-Instanz auf demselben Rechner erlauben | `false` |
|
| `GIT_ALLOW_LOOPBACK` | Git-Instanz auf demselben Rechner erlauben | `false` |
|
||||||
| `GIT_BLOCK_PRIVATE` | Zugriff ins private LAN sperren | `false` |
|
| `GIT_BLOCK_PRIVATE` | Zugriff ins private LAN sperren | `false` |
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# ─────────────────────────────────────────────────────────────
|
||||||
|
# Overlay fuer den Serverbetrieb: kein Port geht nach aussen.
|
||||||
|
#
|
||||||
|
# Gedacht fuer einen Docker-Host, auf dem mehrere Stacks liegen und ein
|
||||||
|
# gemeinsamer Reverse Proxy die einzige Tuer nach draussen ist. Weder die
|
||||||
|
# Datenbank noch die Anwendung veroeffentlichen dann einen Host-Port; der
|
||||||
|
# Proxy erreicht Nebula ueber ein geteiltes Docker-Netz.
|
||||||
|
#
|
||||||
|
# Aufruf:
|
||||||
|
# docker compose -f docker-compose.yml -f docker-compose.server.yml \
|
||||||
|
# --profile full up -d --build
|
||||||
|
#
|
||||||
|
# Bequemer ist, das dauerhaft in die .env zu schreiben:
|
||||||
|
# COMPOSE_FILE=docker-compose.yml:docker-compose.server.yml
|
||||||
|
# COMPOSE_PROFILES=full
|
||||||
|
# Danach genuegt ueberall "docker compose up -d" – auch in update.sh.
|
||||||
|
#
|
||||||
|
# Das Netz muss es vorher geben (der Proxy-Stack legt es ueblicherweise an):
|
||||||
|
# docker network create proxy
|
||||||
|
#
|
||||||
|
# Im Proxy zeigt das Ziel dann auf http://nebula:3000 bzw. nebula-app:3000.
|
||||||
|
# ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
# !reset loescht die Portfreigabe aus der Basisdatei. Ohne dieses Tag
|
||||||
|
# wuerden die Listen zusammengefuegt statt ersetzt – Compose behandelt
|
||||||
|
# "ports" beim Zusammenfuehren als anzuhaengende Liste.
|
||||||
|
ports: !reset []
|
||||||
|
|
||||||
|
app:
|
||||||
|
ports: !reset []
|
||||||
|
networks:
|
||||||
|
# Das stackeigene Netz: darueber spricht die Anwendung mit der Datenbank.
|
||||||
|
default:
|
||||||
|
# Das geteilte Netz zum Reverse Proxy. Der Alias ist der Name, unter dem
|
||||||
|
# der Proxy Nebula anspricht – kurz und eindeutig, denn "app" hiessen in
|
||||||
|
# einem geteilten Netz schnell mehrere Container.
|
||||||
|
proxy:
|
||||||
|
aliases:
|
||||||
|
- ${PROXY_ALIAS:-nebula}
|
||||||
|
|
||||||
|
networks:
|
||||||
|
proxy:
|
||||||
|
external: true
|
||||||
|
name: ${PROXY_NETWORK:-proxy}
|
||||||
+6
-2
@@ -26,7 +26,11 @@ services:
|
|||||||
# Deterministische Sortierung unabhaengig vom Host-Locale
|
# Deterministische Sortierung unabhaengig vom Host-Locale
|
||||||
POSTGRES_INITDB_ARGS: "--encoding=UTF8"
|
POSTGRES_INITDB_ARGS: "--encoding=UTF8"
|
||||||
ports:
|
ports:
|
||||||
- "${POSTGRES_PORT:-5432}:5432"
|
# Nur fuer die Entwicklung – die Anwendung erreicht die Datenbank im
|
||||||
|
# Container ueber "db:5432", nicht ueber den Host. Deshalb an 127.0.0.1
|
||||||
|
# gebunden. Im Serverbetrieb faellt die Freigabe ganz weg, siehe
|
||||||
|
# docker-compose.server.yml.
|
||||||
|
- "${POSTGRES_PORT:-127.0.0.1:5432}:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- nebula-db-data:/var/lib/postgresql/data
|
- nebula-db-data:/var/lib/postgresql/data
|
||||||
healthcheck:
|
healthcheck:
|
||||||
@@ -87,7 +91,7 @@ services:
|
|||||||
ADMINER_DEFAULT_SERVER: db
|
ADMINER_DEFAULT_SERVER: db
|
||||||
ADMINER_DESIGN: dracula
|
ADMINER_DESIGN: dracula
|
||||||
ports:
|
ports:
|
||||||
- "${ADMINER_PORT:-8080}:8080"
|
- "${ADMINER_PORT:-127.0.0.1:8080}:8080"
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
nebula-db-data:
|
nebula-db-data:
|
||||||
|
|||||||
+12
-7
@@ -164,19 +164,24 @@ if [ "$gesund" -ne 1 ]; then
|
|||||||
fi
|
fi
|
||||||
ok "Container meldet sich gesund"
|
ok "Container meldet sich gesund"
|
||||||
|
|
||||||
# Die Anzeige der Adresse: aus APP_PORT kann "3000" oder "127.0.0.1:3000"
|
# Die Anzeige der Adresse. Nicht die .env raten, sondern Docker fragen: mit
|
||||||
# kommen – fuer die Ausgabe zaehlt nur, was hinter dem letzten Doppelpunkt steht.
|
# docker-compose.server.yml veroeffentlicht der Container gar keinen Port und
|
||||||
port_roh="$(sed -n 's/^[[:space:]]*APP_PORT[[:space:]]*=[[:space:]]*"\{0,1\}\([^"#]*\)"\{0,1\}[[:space:]]*$/\1/p' "$root/.env" | tail -n 1)"
|
# haengt nur im Netz des Reverse Proxy. Dann gibt "port" nichts zurueck.
|
||||||
port_roh="${port_roh:-3000}"
|
adresse="$(docker compose --profile full port app 3000 2>/dev/null | tail -n 1 || true)"
|
||||||
port="${port_roh##*:}"
|
|
||||||
|
|
||||||
if command -v curl >/dev/null 2>&1; then
|
if [ -n "$adresse" ]; then
|
||||||
|
port="${adresse##*:}"
|
||||||
|
if command -v curl >/dev/null 2>&1; then
|
||||||
antwort_json="$(curl -fsS "http://127.0.0.1:${port}/api/health" 2>/dev/null || true)"
|
antwort_json="$(curl -fsS "http://127.0.0.1:${port}/api/health" 2>/dev/null || true)"
|
||||||
[ -n "$antwort_json" ] && ok "Antwort: $antwort_json"
|
[ -n "$antwort_json" ] && ok "Antwort: $antwort_json"
|
||||||
|
fi
|
||||||
|
wo="$adresse"
|
||||||
|
else
|
||||||
|
wo="im Docker-Netz (kein Host-Port veroeffentlicht)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Fertig ───────────────────────────────────────────────────
|
# ── Fertig ───────────────────────────────────────────────────
|
||||||
version_jetzt="$(paket_version)"
|
version_jetzt="$(paket_version)"
|
||||||
printf '\n%s%s✓ Nebula v%s laeuft auf %s%s\n' "$gruen" "$fett" "$version_jetzt" "$port_roh" "$aus"
|
printf '\n%s%s✓ Nebula v%s laeuft auf %s%s\n' "$gruen" "$fett" "$version_jetzt" "$wo" "$aus"
|
||||||
[ -n "$sicherung" ] && printf ' Sicherung vor dem Update: %s\n' "$sicherung"
|
[ -n "$sicherung" ] && printf ' Sicherung vor dem Update: %s\n' "$sicherung"
|
||||||
printf ' Log mitlesen: docker compose --profile full logs -f app\n\n'
|
printf ' Log mitlesen: docker compose --profile full logs -f app\n\n'
|
||||||
|
|||||||
Reference in New Issue
Block a user