Documentat fluxul complet modificare → redeploy: manual vs auto-deploy prin webhook Gitea/GitHub. Tabel repo-uri curente per serviciu. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
531 lines
16 KiB
Markdown
531 lines
16 KiB
Markdown
# LXC 103 - Dokploy + Traefik (Control Plane Public)
|
|
|
|
## Informații Generale
|
|
|
|
- **CTID:** 103
|
|
- **IP:** 10.0.20.167
|
|
- **Rol:** Dokploy Control Plane + Traefik routing public
|
|
- **Host Proxmox:** pvemini (10.0.20.201)
|
|
- **Status:** Running (onboot: enabled)
|
|
|
|
---
|
|
|
|
## Arhitectura
|
|
|
|
LXC 103 este nodul central pentru deployment-ul aplicațiilor publice ROMFAST.
|
|
|
|
```
|
|
Internet → 188.26.14.103 → VM 201 IIS (SSL termination)
|
|
│
|
|
*.roa.romfast.ro, roa-qr, dokploy
|
|
│
|
|
LXC 103 :443 (Traefik)
|
|
├── dokploy.romfast.ro → Dokploy UI :3000
|
|
├── roa-qr.romfast.ro → pdf-qr-app container
|
|
├── space.roa.romfast.ro → space-booking container
|
|
├── app1.roa.romfast.ro → app1 container
|
|
└── app2.roa.romfast.ro → app2 container
|
|
```
|
|
|
|
### De ce app-urile publice stau pe LXC 103
|
|
|
|
Dokploy instalează Traefik SEPARAT pe fiecare server. Traefik LXC 103 și
|
|
Traefik LXC 100 nu comunică între ele. Wildcadul `*.roa.romfast.ro` poate fi
|
|
conectat doar la UN singur Traefik — LXC 103.
|
|
|
|
**LXC 100** se folosește pentru:
|
|
- Backend-uri consumate intern (fără DNS public)
|
|
- Job-uri cron, workers, servicii administrative
|
|
|
|
---
|
|
|
|
## Servicii Instalate
|
|
|
|
| Serviciu | Port | Descriere |
|
|
|----------|------|-----------|
|
|
| Dokploy UI | 3000 | Management deployment, CI/CD |
|
|
| Traefik | 443 | Routing HTTPS pentru toate app-urile publice |
|
|
| Traefik Dashboard | 8080 | Monitoring routelor (intern) |
|
|
|
|
---
|
|
|
|
## Domenii Gestionate
|
|
|
|
| Domeniu | Destinație |
|
|
|---------|-----------|
|
|
| `dokploy.romfast.ro` | Dokploy UI (port 3000) |
|
|
| `roa-qr.romfast.ro` | pdf-qr-app container |
|
|
| `space.roa.romfast.ro` | space-booking container |
|
|
| `*.roa.romfast.ro` | Orice app deployată prin Dokploy |
|
|
|
|
---
|
|
|
|
## Setup Inițial
|
|
|
|
### Pasul 1 — Oprire nginx existent pe LXC 100
|
|
|
|
> **Executat pe LXC 100** (10.0.20.170) via Portainer terminal sau Proxmox console:
|
|
|
|
```bash
|
|
docker stop docker-nginx-1
|
|
docker rm docker-nginx-1
|
|
|
|
# Verifică porturile sunt libere
|
|
ss -tlnp | grep -E ':80|:443'
|
|
```
|
|
|
|
### Pasul 2 — Generare SSH Key în Dokploy + Adăugare LXC 100
|
|
|
|
**În Dokploy UI** (https://dokploy.romfast.ro):
|
|
1. Settings → SSH Keys → **Create SSH Key**
|
|
2. Copiază public key-ul generat
|
|
|
|
**Pe LXC 100** (10.0.20.170):
|
|
```bash
|
|
echo "ssh-ed25519 AAAA...[cheia copiată din Dokploy]" >> /root/.ssh/authorized_keys
|
|
chmod 600 /root/.ssh/authorized_keys
|
|
```
|
|
|
|
**În Dokploy UI:**
|
|
1. Servers → **Add Server**
|
|
2. IP: `10.0.20.170`, User: `root`
|
|
3. **Test Connection** → **Setup Server**
|
|
4. Verificare: pe LXC 100, `docker ps` → trebuie container Traefik
|
|
|
|
### Pasul 3 — Deploy pdf-qr-app pe LXC 103
|
|
|
|
**În Dokploy UI:**
|
|
1. Services → **Create Service** → Docker Compose
|
|
2. **Server:** LXC 103 (local)
|
|
3. Docker Compose: conținutul app-ului pdf-qr (vezi `docs/pdf-qr-app.md`)
|
|
4. Domain: `roa-qr.romfast.ro`
|
|
5. **Deploy**
|
|
|
|
---
|
|
|
|
## Tipuri de Servicii în Dokploy — Când să Alegi Ce
|
|
|
|
Dokploy v0.28.2 oferă trei tipuri de servicii. Alegerea corectă evită probleme
|
|
de rețea și simplifică mentenanța.
|
|
|
|
### Comparație rapidă
|
|
|
|
| Criteriu | Application | Compose | Database |
|
|
|----------|-------------|---------|----------|
|
|
| Nr. containere | 1 | 2+ | 1 (managed) |
|
|
| Comunicare inter-servicii | ✗ | ✓ | — |
|
|
| Gestionat de Swarm | ✓ | ✗ | ✓ |
|
|
| Race condition overlay network | nu | **da** (mitigat) | nu |
|
|
| Traefik routing | `.yml` static | Docker labels | — |
|
|
| Build din Git | ✓ | ✓ | — |
|
|
| Build automat fără Dockerfile | ✓ (Nixpacks) | ✗ | — |
|
|
|
|
---
|
|
|
|
### Tip 1: Application — un singur container
|
|
|
|
**Când îl alegi:**
|
|
- Aplicație simplă: un singur proces (API, frontend static, bot, worker)
|
|
- Nu are nevoie să vorbească cu alt container din același proiect
|
|
- Exemple pe LXC 103: `roa-qr`, `qr-generator`, `icon-generator`
|
|
|
|
**Avantaje:** Gestionat nativ de Swarm — nu are race condition la restart.
|
|
Traefik primește config static (`.yml` în `/etc/dokploy/traefik/dynamic/`),
|
|
persistent indiferent de starea containerului.
|
|
|
|
**Pași în Dokploy UI:**
|
|
|
|
```
|
|
1. Projects → [proiect] → Add Service → Application
|
|
|
|
2. Tab General:
|
|
- Name: numeapp
|
|
- Provider: GitHub / GitLab / Gitea / URL public
|
|
- Repository + Branch
|
|
|
|
3. Tab Build:
|
|
- Build Type:
|
|
Dockerfile → dacă repo-ul are Dockerfile (recomandat pentru control)
|
|
Nixpacks → dacă nu ai Dockerfile, Dokploy detectează limbajul automat
|
|
Docker Image → dacă folosești o imagine deja construită (ex: nginx:alpine)
|
|
|
|
4. Tab Domains → Add Domain:
|
|
- Host: numeapp.roa.romfast.ro
|
|
- Container Port: portul pe care ascultă aplicația (ex: 80, 3000, 8000)
|
|
- HTTPS: OFF (SSL e pe IIS VM 201)
|
|
- Certificate: None
|
|
|
|
5. Tab Environment → adaugă variabile de mediu dacă e nevoie
|
|
|
|
6. → Deploy
|
|
```
|
|
|
|
---
|
|
|
|
### Tip 2: Compose — mai multe containere
|
|
|
|
**Când îl alegi:**
|
|
- Aplicația are frontend + backend care comunică intern
|
|
(ex: nginx face `proxy_pass http://backend:8000`)
|
|
- Ai nevoie de o rețea bridge shared între servicii
|
|
- Exemple pe LXC 103: `space-booking` (frontend nginx + backend Python)
|
|
|
|
**Atenție:** Docker Compose pe Swarm are race condition la restart Docker
|
|
(bug Dokploy #2033, docker/compose #12862). Mitigat pe acest server prin
|
|
`dokploy-compose-heal.service` — **fără intervenție manuală la restart**.
|
|
|
|
**Cerințe obligatorii în `docker-compose.yml`:**
|
|
|
|
```yaml
|
|
services:
|
|
frontend:
|
|
# ...
|
|
restart: unless-stopped # obligatoriu pe toate serviciile
|
|
networks:
|
|
- internal
|
|
- dokploy-network # obligatoriu pe serviciul cu Traefik
|
|
labels:
|
|
- traefik.enable=true
|
|
# ... (labels Traefik, populate automat de Dokploy la Add Domain)
|
|
|
|
backend:
|
|
# ...
|
|
restart: unless-stopped
|
|
networks:
|
|
- internal # suficient dacă nu e accesat direct din exterior
|
|
|
|
networks:
|
|
internal:
|
|
driver: bridge
|
|
dokploy-network:
|
|
external: true # rețeaua Swarm gestionată de Dokploy
|
|
```
|
|
|
|
**Pași în Dokploy UI:**
|
|
|
|
```
|
|
1. Projects → [proiect] → Add Service → Compose
|
|
|
|
2. Tab General:
|
|
- Name: numeapp
|
|
- Provider: GitHub / GitLab / Gitea / URL public
|
|
- Repository + Branch
|
|
- Compose File Path: docker-compose.yml (sau subpath dacă e în subdirector)
|
|
|
|
3. Tab Domains → Add Domain: ← OBLIGATORIU prin UI, nu doar în labels!
|
|
- Service Name: alege serviciul care are portul 80 (ex: frontend)
|
|
- Host: numeapp.roa.romfast.ro
|
|
- Container Port: 80
|
|
- HTTPS: OFF
|
|
- Certificate: None
|
|
→ Dokploy generează .yml static în Traefik dynamic config
|
|
|
|
4. Tab Environment → variabile de mediu
|
|
|
|
5. → Deploy
|
|
```
|
|
|
|
> **Important:** Dacă adaugi domeniul NUMAI în labels din docker-compose.yml
|
|
> (fără să-l înregistrezi în UI), Traefik pierde ruta când containerul e oprit.
|
|
> Înregistrarea prin UI generează config static persistent.
|
|
|
|
---
|
|
|
|
### Tip 3: Database — baze de date gestionate
|
|
|
|
**Când îl alegi:**
|
|
- Ai nevoie de PostgreSQL, MySQL, MongoDB, Redis, MariaDB
|
|
- Preferi ca Dokploy să gestioneze backupurile și volumele
|
|
- Alternativa: include DB direct în docker-compose.yml al aplicației
|
|
|
|
**Pași:**
|
|
```
|
|
1. Projects → [proiect] → Add Service → Database
|
|
2. Alege tipul (PostgreSQL / MySQL / Redis etc.)
|
|
3. Configurează credențialele și versiunea
|
|
4. → Create
|
|
```
|
|
|
|
Conexiunea din aplicație folosește hostname-ul serviciului pe `dokploy-network`.
|
|
|
|
---
|
|
|
|
## Workflow: Modificare Cod + Redeploy
|
|
|
|
Toate serviciile sunt legate la Git. Fluxul pentru orice modificare:
|
|
|
|
```
|
|
1. Modifici fișierele local (docker-compose.yml, Dockerfile, cod sursă etc.)
|
|
2. git commit + git push → repo-ul sursă (Gitea / GitHub)
|
|
3. Dokploy preia modificările și face redeploy
|
|
```
|
|
|
|
**Pasul 3 are două variante:**
|
|
|
|
### Varianta A — Redeploy manual
|
|
|
|
Intri în Dokploy UI → proiect → serviciu → tab **Deployments** → buton **Deploy**.
|
|
Dokploy face `git pull` + rebuild + restart. Necesar dacă nu e configurat webhook.
|
|
|
|
### Varianta B — Auto-deploy prin webhook (recomandat)
|
|
|
|
La fiecare `git push`, Gitea/GitHub notifică automat Dokploy → redeploy fără intervenție.
|
|
|
|
**Configurare o singură dată per serviciu:**
|
|
|
|
```
|
|
Dokploy UI → proiect → serviciu → tab General → Auto Deploy → ON
|
|
→ copiază Webhook URL afișat
|
|
|
|
Gitea (gitea.romfast.ro):
|
|
repo → Settings → Webhooks → Add Webhook
|
|
Payload URL: [webhook-ul din Dokploy]
|
|
Content Type: application/json
|
|
Trigger: Push events
|
|
→ Save
|
|
|
|
GitHub:
|
|
repo → Settings → Webhooks → Add webhook
|
|
Payload URL: [webhook-ul din Dokploy]
|
|
Content Type: application/json
|
|
Events: Just the push event
|
|
→ Add webhook
|
|
```
|
|
|
|
**Repo-uri curente și sursa lor:**
|
|
|
|
| Serviciu | Repo | Branch |
|
|
|----------|------|--------|
|
|
| space-booking (Compose) | `gitea.romfast.ro/romfast/space-booking` | master |
|
|
| icon-generator (Application) | `github.com/Romfast/icon-generator` | main |
|
|
| qr-pdfqrapp (Application) | `gitea.romfast.ro` (detalii în Dokploy UI) | — |
|
|
| qr-qrgenerator (Application) | `gitea.romfast.ro` (detalii în Dokploy UI) | — |
|
|
|
|
---
|
|
|
|
## Workflow: Adăugare App Nouă (Rezumat)
|
|
|
|
```
|
|
1. Alege tipul:
|
|
- Un container, fără comunicare internă → Application
|
|
- Frontend + backend care comunică intern → Compose
|
|
- Bază de date → Database
|
|
|
|
2. Dokploy UI → Projects → [proiect] → Add Service → [tipul ales]
|
|
|
|
3. DNS: numeapp.roa.romfast.ro → acoperit automat de wildcard *.roa.romfast.ro
|
|
(nu trebuie A record nou dacă e sub *.roa.romfast.ro)
|
|
|
|
4. SSL: certificatul wildcard de pe VM 201 acoperă automat subdomeniile noi
|
|
(Win-ACME — necesită reînnoire manuală doar dacă adaugi domenii non-wildcard)
|
|
|
|
5. Domeniu: adaugă ÎNTOTDEAUNA prin tab Domains în Dokploy UI
|
|
→ nu doar în docker-compose.yml labels
|
|
|
|
6. Deploy → verificare: curl -I https://numeapp.roa.romfast.ro/
|
|
```
|
|
|
|
---
|
|
|
|
## Verificare
|
|
|
|
```bash
|
|
# Traefik funcționează (din LAN)
|
|
curl -I https://10.0.20.167/
|
|
|
|
# Dokploy UI accesibil
|
|
curl -I https://dokploy.romfast.ro/
|
|
|
|
# pdf-qr-app accesibil
|
|
curl -I https://roa-qr.romfast.ro/
|
|
|
|
# Test wildcard (după deploy app cu hostname)
|
|
curl -I https://app1.roa.romfast.ro/
|
|
|
|
# LXC 100 Portainer funcționează în continuare
|
|
curl -sk https://10.0.20.170:9443/api/status
|
|
```
|
|
|
|
---
|
|
|
|
## Configurare Traefik (gestionat automat de Dokploy)
|
|
|
|
Traefik pe LXC 103 este configurat și actualizat automat de Dokploy la fiecare
|
|
deploy. Nu modificați manual configurația Traefik fără să înțelegeți impactul.
|
|
|
|
```bash
|
|
# Status containere pe LXC 103
|
|
docker ps
|
|
|
|
# Logs Traefik
|
|
docker logs traefik -f
|
|
|
|
# Verificare routere Traefik
|
|
curl http://localhost:8080/api/http/routers | jq .
|
|
```
|
|
|
|
---
|
|
|
|
## Fix: Docker Swarm VIP DNS (Bug Permanent Rezolvat)
|
|
|
|
### Problema
|
|
|
|
Docker Swarm folosește implicit **VIP (Virtual IP)** pentru load balancing intern.
|
|
IPVS (mecanismul kernel care implementează VIP) **nu funcționează în LXC containers**.
|
|
|
|
Când Traefik încearcă să rezolve `http://qr-qrgenerator-vqkwsu:80`, primea VIP-ul
|
|
serviciului (ex: 10.0.1.8) în loc de IP-ul real al task-ului (ex: 10.0.1.12) →
|
|
**502 Bad Gateway**.
|
|
|
|
### Soluția
|
|
|
|
Schimbăm endpoint mode-ul serviciilor Swarm din `vip` → `dnsrr` (DNS Round Robin).
|
|
Cu `dnsrr`, DNS-ul rezolvă direct la IP-ul real al containerului, bypassing IPVS.
|
|
|
|
### Implementare: `dokploy-dnsrr-fix` (systemd service)
|
|
|
|
Un listener permanent care prinde orice serviciu Swarm nou creat/actualizat de
|
|
Dokploy și îl setează automat la `dnsrr`:
|
|
|
|
```bash
|
|
# Script: /usr/local/bin/dokploy-dnsrr-fix.sh
|
|
# Service: /etc/systemd/system/dokploy-dnsrr-fix.service
|
|
```
|
|
|
|
```bash
|
|
# Verificare status
|
|
systemctl status dokploy-dnsrr-fix
|
|
|
|
# Logs (confirmare că fixul s-a aplicat la ultimul deploy)
|
|
journalctl -u dokploy-dnsrr-fix -n 20
|
|
```
|
|
|
|
Output normal după un deploy:
|
|
```
|
|
Setting dnsrr for qr-qrgenerator-vqkwsu (was: vip)
|
|
OK - qr-qrgenerator-vqkwsu now uses dnsrr
|
|
```
|
|
|
|
### Timeline la fiecare redeploy
|
|
|
|
```
|
|
0s Dokploy creează/actualizează serviciul Swarm (vip mode)
|
|
→ Traefik: 502 Bad Gateway
|
|
1s Listener prinde evenimentul update
|
|
3s docker service update --endpoint-mode dnsrr
|
|
13s Service converge, DNS rezolvă corect
|
|
→ Traefik: 200 OK
|
|
```
|
|
|
|
**Downtime la redeploy: ~13 secunde** (inevitabil cu Swarm în LXC).
|
|
|
|
### Dacă fix-ul nu funcționează (manual recovery)
|
|
|
|
```bash
|
|
# 1. Identifică serviciul cu problemă
|
|
docker service ls --format '{{.Name}} {{.Mode}}'
|
|
|
|
# 2. Setează manual dnsrr
|
|
docker service update --endpoint-mode dnsrr <service-name>
|
|
|
|
# 3. Repornește listener dacă e oprit
|
|
systemctl restart dokploy-dnsrr-fix
|
|
```
|
|
|
|
---
|
|
|
|
## Fix: Docker Compose — Overlay Network Race Condition la Restart (Bug Permanent Rezolvat)
|
|
|
|
### Problema
|
|
|
|
Proiectele **Docker Compose** din Dokploy (ex: space-booking) care folosesc
|
|
`dokploy-network` ca rețea externă (`external: true`) **pică la restart Docker**.
|
|
|
|
Eroarea din `docker inspect <container>`:
|
|
```
|
|
failed to set up container networking: could not find a network matching
|
|
network mode dokploy-network: network dokploy-network not found
|
|
```
|
|
Container-ul iese cu **exit code 128** și nu mai pornește singur.
|
|
|
|
### Cauza
|
|
|
|
Race condition confirmat în:
|
|
- **Dokploy** issues #2033, #1802 — fără fix livrat
|
|
- **Docker Compose** upstream issue #12862 — nerezolvat (aprilie 2026)
|
|
|
|
La restart Docker daemon:
|
|
1. Docker Swarm inițializează rețelele overlay (`dokploy-network`) — durează câteva secunde
|
|
2. Docker Compose containers cu `restart: unless-stopped` pornesc **imediat**
|
|
3. `dokploy-network` nu e încă disponibilă → exit 128
|
|
|
|
Serviciile **Swarm** (Application type în Dokploy) nu au această problemă —
|
|
orchestratorul știe să aștepte rețelele înainte de a programa task-urile.
|
|
|
|
### De ce Docker Compose în loc de Application (Swarm)
|
|
|
|
Proiectele multi-serviciu (ex: frontend + backend) rămân Compose deoarece
|
|
serviciile comunică intern între ele (ex: nginx `proxy_pass http://backend:8000`
|
|
pe o rețea bridge shared). Conversia la Swarm Application ar necesita modificări
|
|
în codul aplicației.
|
|
|
|
### Soluția
|
|
|
|
Script generic care rulează la boot, așteaptă `dokploy-network` și repornește
|
|
**orice** container Compose eșuat din cauza lipsei rețelei:
|
|
|
|
```bash
|
|
# Script: /usr/local/bin/dokploy-compose-heal.sh
|
|
# Service: /etc/systemd/system/dokploy-compose-heal.service (enabled)
|
|
```
|
|
|
|
Logica scriptului:
|
|
- Polling `dokploy-network` disponibil (max 90s, interval 3s)
|
|
- Caută containere cu `exit code 128` + eroare "network" + label `com.docker.compose.project`
|
|
- Le repornește — **nu atinge** containerele oprite intenționat (exit 0)
|
|
|
|
```bash
|
|
# Verificare status
|
|
systemctl status dokploy-compose-heal
|
|
|
|
# Logs la ultimul boot
|
|
journalctl -u dokploy-compose-heal -n 30
|
|
|
|
# Rulare manuală (după un docker restart neașteptat)
|
|
systemctl start dokploy-compose-heal
|
|
```
|
|
|
|
### Fix aditional: DNS Docker daemon
|
|
|
|
Docker containers foloseau implicit DNS-ul Tailscale (`100.100.100.100`)
|
|
care **nu e accesibil** din interiorul containerelor — Dokploy primea
|
|
`ESERVFAIL` la validarea domeniilor.
|
|
|
|
Fix aplicat în `/etc/docker/daemon.json`:
|
|
```json
|
|
{"dns": ["8.8.4.4", "8.8.8.8"]}
|
|
```
|
|
|
|
### Pentru orice proiect Compose nou în Dokploy
|
|
|
|
1. Adaugă domeniul în **Dokploy UI → Domains → Add Domain** (nu doar în labels din docker-compose.yml)
|
|
— Dokploy generează `.yml` static în Traefik dynamic config, persistent la restart
|
|
2. Dacă containerul pică după restart Docker → `systemctl start dokploy-compose-heal` rezolvă automat
|
|
|
|
---
|
|
|
|
## Documentație Asociată
|
|
|
|
- **Arhitectură completă IIS:** `../vm201-windows/docs/vm201-dokploy-infrastructure.md`
|
|
- **Setup IIS VM 201:** `../vm201-windows/scripts/setup-new-iis-sites.ps1`
|
|
- **Web.config IIS proxy:** `../vm201-windows/iis-configs/`
|
|
- **LXC 100 (Remote Node):** `../lxc100-portainer/README.md` *(de creat)*
|
|
- **space-booking deploy guide:** `docs/space-booking-app.md`
|
|
- **pdf-qr-app deploy guide:** `docs/pdf-qr-app.md`
|
|
|
|
---
|
|
|
|
**Ultima actualizare:** 2026-04-28
|
|
**Autor:** Marius Mutu
|
|
**Proiect:** ROMFASTSQL - LXC 103 Dokploy
|