Each customer runs as an isolated Docker Compose stack on the same server.
Traefik handles TLS and routing by hostname — no port conflicts.
Internet
│
▼
Traefik (port 443, shared)
├── Host(hampex.duckdns.org) → hampex stack
└── Host(sunlife-solar.duckdns.org) → sunlife stack
Each stack is isolated by:
CUSTOMER_PREFIX — no name collision| Customer | Domain | STACK_NAME | CUSTOMER_PREFIX |
|---|---|---|---|
| Hampex | hampex.duckdns.org | hampex | hampex |
| Sunlife Solar | sunlife-solar.duckdns.org | sunlife | sl |
| Service | Description |
|---|---|
| postgres | PostgreSQL 17 — isolated per stack |
| keycloak | Keycloak 26 — auth, isolated |
| backend | Spring Boot — REST API on /api |
| frontend | Angular + Nginx — served on / |
Note: This process will be simplified with a setup script in the future. For now, follow these steps manually via SSH.
docker login registry.gitlab.comsunlife-solar)hampex.duckdns.orgVerify DNS resolves:
dig +short sunlife-solar.duckdns.org
ssh -i ~/.ssh/mh_deploy_key deployer@netcup
mkdir -p ~/sunlife-solar
cat > ~/sunlife-solar/.env << 'EOF'
CUSTOMER_PREFIX=sl
STACK_NAME=sunlife
HOST=sunlife-solar.duckdns.org
REGISTRY=registry.gitlab.com/clean-tracker/hampex-store
VERSION=CHANGE_ME
DB_NAME=sunlife
DB_USER=sunlife
DB_PASS=CHANGE_ME
KC_ADMIN_USER=admin
KC_ADMIN_PASS=CHANGE_ME
KC_BACKEND_SECRET=CHANGE_ME
EOF
Generate secure passwords (run 3 times — for DB_PASS, KC_ADMIN_PASS, KC_BACKEND_SECRET):
openssl rand -base64 32
Then fill in the passwords:
nano ~/sunlife-solar/.env
cp ~/hampex-store/keycloak/realm-hampex.json ~/sunlife-solar/
cp ~/hampex-store/deployment/docker/docker-compose.yml ~/sunlife-solar/
Fix the realm mount path:
sed -i 's|../../keycloak/realm-hampex.json|/home/deployer/sunlife-solar/realm-hampex.json|' \
~/sunlife-solar/docker-compose.yml
Verify:
grep realm ~/sunlife-solar/docker-compose.yml
Find the version currently running on Hampex:
docker inspect hampex-backend-1 --format='{{range .Config.Env}}{{println .}}{{end}}' | grep APP_VERSION
Set it in the .env:
nano ~/sunlife-solar/.env
# Set VERSION=main-xxxxxxxx-YYYYMMDD-HHMM
docker compose -p sunlife \
--env-file ~/sunlife-solar/.env \
-f ~/sunlife-solar/docker-compose.yml \
up -d
docker compose -p sunlife ps
docker compose -p sunlife logs keycloak --tail=30
docker compose -p sunlife logs backend --tail=30
curl -sk https://sunlife-solar.duckdns.org/api/info
TLS certificate is issued automatically by Let's Encrypt (~30s on first boot).
After first login as admin: Settings → Appearance → App Name → set to customer name (e.g. Sunlife Solar)
Push to main branch — the pipeline builds, pushes images and deploys automatically.
docker compose -p sunlife \
--env-file ~/sunlife-solar/.env \
-f ~/sunlife-solar/docker-compose.yml \
pull
docker compose -p sunlife \
--env-file ~/sunlife-solar/.env \
-f ~/sunlife-solar/docker-compose.yml \
up -d --remove-orphans
docker compose -p sunlife \
--env-file ~/sunlife-solar/.env \
-f ~/sunlife-solar/docker-compose.yml \
restart backend
After a .env change — full recreate of that service:
docker compose -p sunlife \
--env-file ~/sunlife-solar/.env \
-f ~/sunlife-solar/docker-compose.yml \
up -d backend
Stop (keep data):
docker compose -p sunlife down
Remove completely — DESTRUCTIVE, deletes all data:
docker compose -p sunlife down -v
Restart after server reboot:
docker compose -p sunlife \
--env-file ~/sunlife-solar/.env \
-f ~/sunlife-solar/docker-compose.yml \
up -d
| Problem | Command |
|---|---|
| Container not starting | docker compose -p <stack> ps |
| Backend error | docker compose -p <stack> logs backend --tail=50 |
| Keycloak error | docker compose -p <stack> logs keycloak --tail=50 |
| No TLS / cert error | dig +short <domain> — DNS must resolve to server IP |
| 404 on domain | curl -sk http://localhost:8080/api/http/routers \| grep <stack> |
| Server OOM / process killed | free -h — 4 GB RAM fits max 2 stacks comfortably |
| Realm not imported | grep realm ~/customer/docker-compose.yml — check file path |
| Service | RAM |
|---|---|
| Postgres | ~200 MB |
| Keycloak | ~600 MB |
| Backend | ~350 MB |
| Frontend | ~50 MB |
| Total | ~1.2 GB |
Server has 4 GB RAM → max 2 stacks comfortable. Upgrade to 8 GB for 3+.