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:
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):
- Settings → SSH Keys → Create SSH Key
- Copiază public key-ul generat
Pe LXC 100 (10.0.20.170):
echo "ssh-ed25519 AAAA...[cheia copiată din Dokploy]" >> /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys
În Dokploy UI:
- Servers → Add Server
- IP:
10.0.20.170, User:root - Test Connection → Setup Server
- Verificare: pe LXC 100,
docker ps→ trebuie container Traefik
Pasul 3 — Deploy pdf-qr-app pe LXC 103
În Dokploy UI:
- Services → Create Service → Docker Compose
- Server: LXC 103 (local)
- Docker Compose: conținutul app-ului pdf-qr (vezi
docs/pdf-qr-app.md) - Domain:
roa-qr.romfast.ro - 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:
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
# 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.
# 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:
# Script: /usr/local/bin/dokploy-dnsrr-fix.sh
# Service: /etc/systemd/system/dokploy-dnsrr-fix.service
# 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)
# 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:
- Docker Swarm inițializează rețelele overlay (
dokploy-network) — durează câteva secunde - Docker Compose containers cu
restart: unless-stoppedpornesc imediat dokploy-networknu 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:
# Script: /usr/local/bin/dokploy-compose-heal.sh
# Service: /etc/systemd/system/dokploy-compose-heal.service (enabled)
Logica scriptului:
- Polling
dokploy-networkdisponibil (max 90s, interval 3s) - Caută containere cu
exit code 128+ eroare "network" + labelcom.docker.compose.project - Le repornește — nu atinge containerele oprite intenționat (exit 0)
# 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:
{"dns": ["8.8.4.4", "8.8.8.8"]}
Pentru orice proiect Compose nou în Dokploy
- Adaugă domeniul în Dokploy UI → Domains → Add Domain (nu doar în labels din docker-compose.yml)
— Dokploy generează
.ymlstatic în Traefik dynamic config, persistent la restart - Dacă containerul pică după restart Docker →
systemctl start dokploy-compose-healrezolvă 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