SmartBox Infrastructure¶
Docker Compose stack for the entire SmartBox platform.
Stack¶
| Service | Image | Public URL | Auth |
|---|---|---|---|
| Traefik | traefik:latest |
traefik.smartbox.ergoflow.app |
basic auth |
| Postgres | postgres:16-alpine |
internal only | postgres user |
| Redis | redis:7-alpine |
internal only | password |
| RabbitMQ | rabbitmq:3.13-management-alpine |
rabbit.smartbox.ergoflow.app |
RMQ user |
| Keycloak | quay.io/keycloak/keycloak:26.0 |
auth.smartbox.ergoflow.app |
KC admin |
| Gitea | gitea/gitea:1.22 |
git.smartbox.ergoflow.app |
Gitea user |
| Portainer | portainer/portainer-ce:lts |
ops.smartbox.ergoflow.app |
Portainer user |
| Dozzle | amir20/dozzle:latest |
logs.smartbox.ergoflow.app |
basic auth |
| Backend | smartbox/backend:latest (built) |
api.smartbox.ergoflow.app |
OIDC + API key |
| Docs | smartbox/docs:latest (built — MkDocs Material → nginx) |
docs.smartbox.ergoflow.app |
public |
All services share the smartbox Docker network. Only Traefik exposes ports
to the host (80, 443); everything else is reachable only through Traefik.
First-time deploy (fresh Ubuntu 24.04 LTS host)¶
```bash
1. Get the code on the server (rsync/tar from workstation, or git clone)¶
target: /opt/smartbox (chown to your user)¶
2. Install Docker + firewall¶
cd /opt/smartbox/infra make bootstrap
log out and back in so docker group takes effect¶
3. Generate .env with strong random secrets¶
make env
NOTE: copy the printed admin password — it is NOT stored in .env¶
4. Bring infra up (backend will fail until step 6, that's fine)¶
make up
5. Bootstrap Keycloak realm (creates smartbox realm + clients +¶
seed admin user). Run once after Keycloak is healthy.¶
bash keycloak/bootstrap-realm.sh
COPY the printed seed-user password — KC admin has it via UI but¶
this script only reports it on first run.¶
6. Build + deploy backend¶
docker compose build backend docker compose up -d backend ```
Day-to-day¶
bash
make ps # list services + status
make logs # tail all
make logs-backend # tail one
make health # docker ps with status
make update # pull + recreate
make down # stop everything
Deploying backend / panel / kiosk changes¶
The canonical flow today is manual tar | ssh from the workstation — no
registry, no Gitea Actions, no make update for app code. The Gitea remote
is the audit trail; the source of truth that runs is what gets tared up.
```powershell
On workstation (PowerShell):¶
cd C:\Projekty\SmartBox git commit -am "etap-X.Y: …" git push gitea main
Ship backend (or frontend-web) source to the VM¶
tar -czf - backend infra | ssh smartbox-pub "cd /opt/smartbox && tar -xzf -"
Build + restart the affected service on the VM¶
ssh smartbox-pub "cd /opt/smartbox/infra && docker compose build backend && docker compose up -d backend && docker compose logs backend --tail=80" ```
For the kiosk: build a Windows release locally, zip it, scp into the
backend's release volume, then INSERT a MachineRelease +
MachineReleaseAssignment row — the SmartBoxKioskAgent PowerShell task
on the kiosk picks it up within 30 s. Recipe in the project root
README.md → "Pushing an OTA update".
(Will be one git push triggering Gitea Actions in etap 8 — make update
in the table above is the old "pull from registry" path that we don't use.)
Hairpin NAT note¶
If port 443 to the public IP doesn't loop back through the router, the
backend container reaches Keycloak via extra_hosts: host-gateway — the
container resolves auth.smartbox.ergoflow.app to the docker-host gateway,
and from there the request goes through Traefik. JWKS validation, KC admin
calls, and JIT user provisioning all work without a hairpin-capable router.
Subdomain DNS prerequisite¶
These A records must point to the server's public IP before running
make up for the first time, otherwise the HTTP-01 ACME challenge fails:
smartbox.ergoflow.app api.smartbox.ergoflow.app
auth.smartbox.ergoflow.app git.smartbox.ergoflow.app
docs.smartbox.ergoflow.app ops.smartbox.ergoflow.app
logs.smartbox.ergoflow.app traefik.smartbox.ergoflow.app
rabbit.smartbox.ergoflow.app
Files¶
infra/
├── docker-compose.yml
├── .env.example template; .env (gitignored) is generated by gen-env.sh
├── Makefile convenience commands
├── docs/
│ ├── Dockerfile multi-stage MkDocs Material build → nginx alpine
│ └── nginx.conf gzip + cache rules for the static site
├── keycloak/
│ └── bootstrap-realm.sh one-shot realm + clients + seed admin
├── postgres/
│ └── init.sh creates per-app users + databases on first cluster boot
├── scripts/
│ ├── bootstrap-server.sh installs Docker, ufw, make on the host
│ └── gen-env.sh generates .env + traefik admin htpasswd
└── traefik/
└── dynamic/
├── middlewares.yml security headers + admin basic-auth
└── users.htpasswd generated; gitignored
Service-specific first-run notes¶
- Portainer: visit
https://ops.smartbox.ergoflow.appwithin 5 minutes of first start and set the admin password (built-in safety lockout). If you miss the window,docker compose restart portainerresets it. - Keycloak: log in to
https://auth.smartbox.ergoflow.appwithKEYCLOAK_ADMIN/KEYCLOAK_ADMIN_PASSWORDfrom.env. Realmsmartboxis created bybootstrap-realm.sh. - Gitea: log in with the seed admin (created via CLI on first deploy). HTTPS-only push, no SSH.
- RabbitMQ: management UI at
https://rabbit.smartbox.ergoflow.app, default user/password from.env(RABBITMQ_USER,RABBITMQ_PASSWORD). - Backend:
/healthathttps://api.smartbox.ergoflow.app/health, Swagger UI at/docs. JWT bearer orX-Machine-Api-Keyrequired for protected endpoints. - ErgoFlow integration (etap 6): the backend talks to the customer's
upstream HR system at the URL configured under
System → Integracje → ErgoFlow → Synchronizacjain the panel. No infra changes required — the integration runs entirely inside thebackendcontainer. The only outbound dependency is HTTPS access from the host to the configured ErgoFlow base URL (typicallygaros.ergoflow.app).