# Self-hosted deploy: Gitea + Caddy + EMS (`/opt/ems-deploy`) Runbook pro **single-node Debian**, **Docker Compose**, **Gitea Actions** bez Kubernetes. Doplňuje konkrétní stav serveru (ZFS, `zfs` storage driver, Caddy na hostu, Gitea v `/opt/gitea-stack`). --- ## 1. Co kde běží | Oblast | Cesta / adresa | Poznámka | |--------|----------------|----------| | Gitea (HTTP) | `https://git.vojacek.eu` → Caddy → `127.0.0.1:3000` | Gitea publikuje HTTP jen na loopback. | | Gitea (Git SSH) | `git.vojacek.eu:2222` → kontejner `:22` | Host SSH zůstává na `:22`. V `app.ini`: `START_SSH_SERVER = false` (kvůli SSH v base image). | | Gitea stack | `/opt/gitea-stack` | `postgres`, `gitea`, `gitea-runner`; vlastní `docker-compose` + `.env`. | | Gitea runner | stejný stack | `act_runner` s přepsaným `entrypoint`/`command`; registrace ručně → `runner/runner.json`. | | EMS deploy | `/opt/ems-deploy` | Oddělený Compose projekt od Gitea. | | EMS checkout | `/opt/ems-deploy/app` | Git clone stejného repa jako v Gitea. | | EMS Compose (runtime) | `/opt/ems-deploy/docker-compose.yml` | Kopíruje se ze `app/deploy/docker-compose.yml` při každém deployi (`deploy.sh`). | | EMS secrets | `/opt/ems-deploy/.env` | **Není v gitu**; vzor `.env.example` v repu. | **PostgREST v produkčním `deploy/docker-compose.yml`:** služba naslouchá v **Docker síti** na `:3000`, **bez** mapování na host — frontend v kontejneru volá `postgrest:3000`. Tím se vyhne kolizi s Gitea na hostovém `127.0.0.1:3000`. Pokud někdy přidáš `ports` pro ladění, použij jiný host port než 3000. --- ## 2. Architektonický verdikt **Směr (push → Actions → skript na hostu → `docker compose build && up`) je správný** pro cíl „jeden server, žádný registry, žádný DinD“. **Tvrdá fakta / rizika** 1. **Job Gitea Actions neběží na hostu**, ale ve **job kontejneru** (Docker executor). Kroky typu `run: /opt/ems-deploy/deploy.sh` tedy **neuvidí** hostovské cesty, dokud je nepřimountuješ do job kontejneru a nepřidáš je do `valid_volumes` u runneru. 2. **Mount Docker socketu do jobu = plná moc nad hostovským Dockerem** (ekvivalent root pro kontejnery). Je to běžný pattern u self-hosted runnerů; bezpečnost = důvěra k přístupu do repa a k labelům runneru, ne veřejný runner. 3. **`docker: command not found` v jobu** je očekávané u defaultního image — buď použij image s `docker` + `docker compose`, nebo vůbec nevolaj docker uvnitř jobu (u tebe stačí spustit `deploy.sh`, který mluví s hostovským daemonem přes socket). 4. **ZFS + Docker `zfs` driver:** snapshoty a `zfs list` rostou s image vrstvami; `deploy.sh` už dělá `docker image prune -f` (dangling). Hlídej místo na poolu a případně periodický `docker system prune` (opatrně — nesmí mazat věci, co potřebuješ). 5. **`git reset --hard origin/main`:** rychlé a deterministické; jakýkoli lokální drift v `/opt/ems-deploy/app` na serveru se **ztratí**. Na produkční app adresář nesahat ručně — vždy přes git + deploy. 6. **Dvě síťě:** `ems_net` vs `gitea_net` — služby se defaultně nevidí; pro EMS to obvykle nevadí. Integrovat Gitea DB s EMS nechceš. --- ## 3. Tok deploye (cílový) ```mermaid flowchart LR dev[Dev laptop] -->|git push| gitea[Gitea main] gitea --> act[Gitea Actions] act --> runner[act_runner + Docker] runner --> job[Job container Alpine + sock + /opt mount] job --> sh[deploy.sh] sh --> git[git fetch / reset main] sh --> sync[install compose.yml] sh --> dc["docker compose build && up -d"] dc --> hostd[Host Docker Engine] hostd --> ems[EMS stack] ``` 1. Vývoj lokálně, merge do `main`, push na `git.vojacek.eu:2222`. 2. Workflow `.gitea/workflows/deploy.yml` (label `self-hosted`) spustí job. 3. Job kontejner má `docker.sock` a rw mount `/opt/ems-deploy`; nainstaluje `git`, `bash`, `docker-cli`, `docker-cli-compose` (Alpine). 4. `/opt/ems-deploy/deploy.sh`: `flock`, `git` v `app/`, sync `docker-compose.yml`, `docker compose config`, `build`, `up -d`, `image prune -f`. Build probíhá na **hostovském** Dockeru (stejný daemon jako Gitea stack), bez DinD. --- ## 4. Bootstrap serveru (jednorázově) Přesně podle hlavičky v `deploy/deploy.sh`: ```bash sudo mkdir -p /opt/ems-deploy/app sudo chown -R "$USER:$USER" /opt/ems-deploy git clone ssh://git@git.vojacek.eu:2222/vojacekd/ems.git /opt/ems-deploy/app cp /opt/ems-deploy/app/.env.example /opt/ems-deploy/.env chmod 600 /opt/ems-deploy/.env # doplnit secrets / DB / JWT / … install -m 755 /opt/ems-deploy/app/deploy/deploy.sh /opt/ems-deploy/deploy.sh /opt/ems-deploy/deploy.sh ``` Uživatel, pod kterým běží runnerův job (root v Alpine job image), musí mít právo číst `.env`, psát do `app/.git` a volat Docker. Typicky runner běží jako root → ověř oprávnění na `/opt/ems-deploy`. --- ## 5. Gitea runner — nutná úprava `config.yaml` Aby job směl přimountovat hostové cesty, v `runner/config.yaml` (na serveru v `/opt/gitea-stack/runner/config.yaml`) **musí** být v `container.valid_volumes` povolené alespoň: ```yaml container: network: host privileged: false valid_volumes: - /opt/ems-deploy - /var/run/docker.sock ``` Bez toho Actions mounty odmítnou / job spadne. Po změně restart runner kontejneru. `labels` při registraci runneru musí odpovídat `runs-on` ve workflow (např. výchozí `self-hosted`). --- ## 6. Caddy a EMS (až bude veřejná doména) Gitea blok v Caddyfile zůstává. Pro EMS přidej **samostatný** site blok, např.: ```caddy ems.vojacek.eu { encode gzip @api path /rest* handle @api { uri strip_prefix /rest reverse_proxy 127.0.0.1:8080 } handle { reverse_proxy 127.0.0.1:8080 } } ``` **Upřesnění podle skutečného nginx ve frontend image:** pokud API jde přes stejný origin a path `/rest`, výše může stačit jeden `reverse_proxy` na `127.0.0.1:8080` bez stripu — ověř v `frontend` konfiguraci. Pro PostgREST OpenAPI nastav v `.env` / `PGRST_OPENAPI_SERVER_PROXY_URI` veřejnou bázi URL. Produkční compose mapuje frontend na **`127.0.0.1:8080`**, backend na **`127.0.0.1:8000`**. --- ## 7. CI: test vs deploy | Workflow | Účel | Poznámka | |----------|------|----------| | `.gitea/workflows/test.yml` | Smoke (soubory, layout) | `runs-on: ubuntu-latest` stáhne image; nepotřebuje Docker. | | `.gitea/workflows/deploy.yml` | Deploy po pushi na `main` | Job kontejner + mounty + viz sekce 5. | `workflow_dispatch` na deploy umožňuje ruční opakování bez prázdného commitu. --- ## 8. Co záměrně neděláme (zatím) - Privátní Docker registry na stejném stroji. - Buildkit cache export / remote cache. - k3s/Kubernetes. - Spouštění `docker compose` uvnitř jobu bez přístupu k **jednomu** hostovskému daemonu (tj. bez soku bys musel do DinD nebo remote docker). --- ## 9. Rychlá kontrola po deployi ```bash docker compose -f /opt/ems-deploy/docker-compose.yml --env-file /opt/ems-deploy/.env ps curl -sf http://127.0.0.1:8000/docs >/dev/null && echo backend OK curl -sf http://127.0.0.1:8080/ >/dev/null && echo frontend OK ``` --- ## 10. Odkazy v repu - `deploy/deploy.sh` — jediný produkční vstup „co se na serveru spouští“. - `deploy/docker-compose.yml` — šablona runtime Compose (kopíruje se do `/opt/ems-deploy`). - `.gitea/workflows/deploy.yml` — napojení na runner + job kontejner.