refactor(repo): deploy/ heißt jetzt build/ – der Name sagte das Gegenteil
"deploy" klang nach Ausrollen, drin steht das Gegenteil: das Bau-Rezept und das Compose, das aus dem Quelltext baut. Ausgerollt wird mit docker-compose.example.yml im Wurzelverzeichnis. Marc ist genau darüber gestolpert. Zwei Dinge mussten dafür vorher fest werden, sonst hätte das Umbenennen die Entwicklungsdatenbank gekostet: Compose leitet den Projektnamen aus dem Ordnernamen ab und hätte unter "build" ein neues, leeres Projekt mit neuem, leerem Volume angelegt. Der Projektname steht deshalb jetzt im Compose (name: aether), und das Volume trägt seinen alten Namen ausdrücklich weiter (deploy_postgres_data) – unschön, aber die Daten bleiben. Ebenfalls angepasst: .gitignore. Dort stand deploy/.env; nach dem Umbenennen wäre build/.env nur noch von der allgemeinen .env-Zeile gedeckt gewesen. Nachgeprüft nach dem Umzug: Abbild baut, Stapel startet, 113 Dateien in der Datenbank wie vorher, Volume unverändert deploy_postgres_data, GET / → 200. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a7a67da040
commit
471679ff64
7 files changed
+30
-20
No files matched your search
@@ -0,0 +1,68 @@
|
||||
# ── Stage 1: Frontend build ───────────────────────────────────────────────────
|
||||
FROM node:20-alpine AS frontend-builder
|
||||
WORKDIR /app/frontend
|
||||
COPY frontend/package*.json ./
|
||||
RUN npm ci
|
||||
COPY frontend/ ./
|
||||
RUN npm run build
|
||||
|
||||
# ── Stage 2: Backend build ────────────────────────────────────────────────────
|
||||
FROM golang:1.25-alpine AS backend-builder
|
||||
WORKDIR /app
|
||||
RUN apk add --no-cache git
|
||||
COPY backend/go.mod backend/go.sum ./
|
||||
RUN go mod download
|
||||
COPY backend/ ./
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o aether ./cmd/aether
|
||||
|
||||
# ── Stage 3: Final image ──────────────────────────────────────────────────────
|
||||
FROM alpine:3.20
|
||||
# ocrmypdf, tesseract und poppler machen den Inhalt von PDFs und Scans
|
||||
# durchsuchbar (internal/documents). Sie wiegen zusammen rund 330 MB – der
|
||||
# Löwenanteil des Images. Wer das nicht braucht, kann die zweite apk-Zeile
|
||||
# streichen und OCR_ENABLED=false setzen; der Server prüft beim Start selbst,
|
||||
# ob die Programme da sind, und läuft sonst ohne Volltextsuche weiter.
|
||||
#
|
||||
# tesseract-ocr-data-deu/-eng sind die Sprachmodelle. Ohne sie startet
|
||||
# tesseract zwar, erkennt aber nichts – das Paket tesseract-ocr allein bringt
|
||||
# keine mit.
|
||||
RUN apk add --no-cache ca-certificates tzdata && \
|
||||
apk add --no-cache ocrmypdf tesseract-ocr tesseract-ocr-data-deu \
|
||||
tesseract-ocr-data-eng poppler-utils && \
|
||||
addgroup -S aether && adduser -S aether -G aether && \
|
||||
mkdir -p /data/users /data/thumbs /data/tmp && \
|
||||
chown -R aether:aether /data
|
||||
WORKDIR /app
|
||||
|
||||
# Wo die Blobs liegen. Steht **hier**, nicht nur in der .env: Die Voreinstellung
|
||||
# im Code ist "./data" – richtig für ein Binary, das man von Hand startet,
|
||||
# falsch im Container, wo daraus /app/data würde. Genau das ist beim ersten
|
||||
# Aufbau aus der schlanken .env passiert: Migrationen liefen durch, dann
|
||||
# "mkdir /app/data: permission denied". Das Abbild kennt seine eigene
|
||||
# Verzeichnisstruktur besser als jede Beispieldatei; eine .env darf es
|
||||
# weiterhin überschreiben.
|
||||
ENV STORAGE_PATH=/data \
|
||||
TMP_PATH=/data/tmp
|
||||
|
||||
# Tesseract vermisst beim allerersten Aufruf die OpenCL-Geräte und schreibt
|
||||
# dabei einen langen Diagnoseblock nach stderr. ocrmypdf ruft davor
|
||||
# `tesseract --list-langs` auf und liest die Antwort – der Block bringt seinen
|
||||
# Parser aus dem Tritt, und der Aufruf endet mit „Tesseract failed to report
|
||||
# available languages" (Exit 3).
|
||||
#
|
||||
# Das Ergebnis der Vermessung landet als .dat im Arbeitsverzeichnis; ab dann ist
|
||||
# die Ausgabe kurz genug. Ohne diese Zeile scheitert deshalb genau das erste
|
||||
# Dokument nach jedem Neustart eines frischen Containers – gemessen, nicht
|
||||
# vermutet: Versuch 1 Exit 3, Versuch 2 erfolgreich. Der Wiederholungszähler
|
||||
# fängt es zwar auf, aber ein Fehlschlag, den man einbauen kann, gehört nicht
|
||||
# in den Log.
|
||||
RUN tesseract --list-langs >/dev/null 2>&1 || true
|
||||
|
||||
COPY --from=backend-builder /app/aether ./aether
|
||||
COPY --from=frontend-builder /app/frontend/dist ./static
|
||||
|
||||
RUN chown -R aether:aether /app
|
||||
USER aether
|
||||
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["./aether"]
|
||||
@@ -0,0 +1,107 @@
|
||||
# Aether – Entwicklung: aus dem Quelltext bauen.
|
||||
#
|
||||
# docker compose -f build/docker-compose.yml build app
|
||||
# docker compose -f build/docker-compose.yml up -d
|
||||
#
|
||||
# Zum **Betreiben** ist docker-compose.example.yml im Wurzelverzeichnis
|
||||
# gedacht: schlanker und ohne Quelltext, es zieht das fertige Abbild.
|
||||
#
|
||||
# Die Einstellungen kommen aus build/.env – als Vorlage dient die eine
|
||||
# .env.example im Wurzelverzeichnis: cp .env.example build/.env
|
||||
#
|
||||
# Erklärungen zu allem hier: README.md
|
||||
|
||||
# Der Projektname steht hier fest und wird **nicht** aus dem Ordnernamen
|
||||
# abgeleitet. Sonst hinge er daran, wie der Ordner gerade heißt: Beim
|
||||
# Umbenennen von deploy/ nach build/ hätte Compose ein neues, leeres Projekt
|
||||
# angelegt – mit einer neuen, leeren Datenbank.
|
||||
name: aether
|
||||
|
||||
services:
|
||||
app:
|
||||
container_name: aether-app
|
||||
build:
|
||||
# Kontext ist das Wurzelverzeichnis – das Abbild braucht backend/ und
|
||||
# frontend/, nicht nur diesen Ordner.
|
||||
context: ..
|
||||
dockerfile: build/Dockerfile
|
||||
image: gitea.honeyy.duckdns.org/docker/aether:latest
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
user: "${APP_UID:-1000}:${APP_GID:-1000}"
|
||||
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, 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]
|
||||
ports:
|
||||
# Standardmäßig nur loopback – davor gehört dein Reverse Proxy mit TLS.
|
||||
# Zum Ausprobieren im LAN: APP_BIND=0.0.0.0 in der .env. Dann liegt die
|
||||
# Anmeldung allerdings unverschlüsselt im Netz.
|
||||
- "${APP_BIND:-127.0.0.1}:${APP_PORT:-8080}:8080"
|
||||
|
||||
postgres:
|
||||
container_name: aether-db
|
||||
image: postgres:16-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: ${DB_NAME}
|
||||
POSTGRES_USER: ${DB_USER}
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
networks: [internal]
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
# Office – kostet rund 1 GB, deshalb hier im Profil (Entwicklerdatei, immer
|
||||
# von der Kommandozeile): ... --profile office up -d
|
||||
# Die Schnipsel im README haben bewusst kein Profil: Oberflächen wie
|
||||
# Portainer oder Dockge kennen `--profile` nicht, dort entstünde der
|
||||
# Container nie – ohne Fehlermeldung.
|
||||
collabora:
|
||||
container_name: aether-collabora
|
||||
image: collabora/code:latest
|
||||
profiles: ["office"]
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
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]
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
networks: [internal]
|
||||
ports:
|
||||
- "${APP_BIND:-127.0.0.1}:${OFFICE_PORT:-9980}:9980"
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
# Fester Name, aus demselben Grund. Er stammt noch aus der Zeit, als der
|
||||
# Ordner „deploy" hieß – umbenennen hieße hier: neues Volume, leere
|
||||
# Datenbank. Der unschöne Name ist der Preis dafür, die Daten zu behalten.
|
||||
name: deploy_postgres_data
|
||||
# "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:
|
||||
driver: bridge
|
||||
Reference in new issue
Block a user