Deployment
EcoCtrl ships as Docker images published to GHCR. The recommended way to run in production is Docker Compose.
Docker Compose
Three compose files are provided for different scenarios:
| File | Purpose |
|---|---|
compose.yml | Pre-built images from GHCR (default) |
compose.build.yaml | Build all images from local Dockerfiles |
compose.dev.yaml | Development overlay with volume mounts and hot reload |
Pre-built deployment (default)
This brings up PostgreSQL, the API server, the admin dashboard and the public web portal in one command.
The simplest path. docker/compose.yml defines four services: PostgreSQL, the API server, the admin SPA bundle, and the web SPA bundle. The two SPA images bundle the static assets behind Caddy, which rewrites /api/* and /static/* to the API container.
One-time setup
git clone https://github.com/hyooeewee/ecoctrl.git
cd ecoctrl/docker
cp .env.example .env.local
$EDITOR .env.local # set JWT_SECRET (required) and IoT credentials (optional)If the target host can reach GHCR and Docker Hub, run the docker compose command below directly.
Run
docker compose -f compose.yml up --buildServices:
| Service | Port | URL |
|---|---|---|
| Web portal | 8081 | http://<host>:8081 |
| Admin dashboard | 4173 | http://<host>:4173 |
| REST API | 3000 | http://<host>:3000 |
| Swagger UI | 3000 | http://<host>:3000/documentation |
| PostgreSQL | 5432 | internal |
Customizing
- Backend host: edit
apps/admin/.env.localandapps/web/.env.localto pointAPI_BASE_URLat your real backend (or a service name within compose). - Database credentials: change
POSTGRES_USER/PASSWORD/DBincompose.ymland updateDATABASE_URLaccordingly. - CORS: set
CORS_ORIGIN=https://app.example.com,https://admin.example.comin the server's environment.
Stop
docker compose -f compose.yml down # keep data
docker compose -f compose.yml down -v # also wipe Postgres volumeWARNING
The compose.yml Caddyfile is configured for plain HTTP. In production, terminate TLS in front of these containers (Caddy on the host, Cloudflare, an ALB, etc.) and route to the SPA containers over the internal network.
Production checklist
Before exposing EcoCtrl to the public internet:
- [ ] Set a strong `JWT_SECRET` and rotate any defaults.
- [ ] Restrict `CORS_ORIGIN` to your real domains.
- [ ] Use a managed PostgreSQL (or harden your own — TLS, backup, monitoring).
- [ ] Enable HTTPS at the proxy layer for `admin.*`, `app.*`, `api.*`.
- [ ] Configure SMTP — without it, registration / password-reset codes fail silently.
- [ ] If using OAuth, register the production callback URLs with each provider.
- [ ] Schedule database backups (the platform's `backup_schedules` row only stores the _next_ timestamp; real backups are still your responsibility).
- [ ] Limit database role privileges in production: revoke `CREATE DATABASE` so the bootstrap auto-create only runs in dev.
- [ ] Forward server logs to an aggregator (Fastify uses pino — JSON-on-stdout works with everything).