Installation on a Hetzner server
PowerForecast is two deployables: the engine (forecast-api, a container with its Redis sidecar) and the wizard (forecast-wizard, static files). Sign-in, the wizard template and AI assistance come from PowerAdmin (admin-api.planningpowertools.com); PowerForecast has no database of its own.
For the base server (Alpine Linux, SSH, Docker, Nginx, certbot), follow Parts 1 – 3 of PowerSolver's documentation/installation/HETZNER_GUIDE.md.
Architecture
https://forecast.planningpowertools.com Nginx → /var/www/powerforecast (forecast-wizard + Documentation/html)
https://forecast-api.planningpowertools.com Nginx → localhost:8020 (forecast-api)
├── Redis (powerforecast-redis, compose network): running jobs
└── volume powerforecast-data: stored forecasts (SQLite)
| Service | Port | Public URL |
|---|---|---|
| forecast-api | 8020 | https://forecast-api.planningpowertools.com |
| Redis | 6379 inside the compose network (6380 on the host, keep it closed) | none |
| forecast-wizard | static | https://forecast.planningpowertools.com |
1. The engine
Build on the development machine, copy, load:
cd D:\PowerProjects\PowerForecast\forecast-api
docker build -t powerforecast-api:latest .
docker save powerforecast-api:latest -o powerforecast-api.tar
scp powerforecast-api.tar root@YOUR_SERVER_IP:/root/
docker load -i /root/powerforecast-api.tar && rm /root/powerforecast-api.tar
mkdir -p /root/powerforecast && cd /root/powerforecast
Copy forecast-api/docker-compose.yml from the repository into /root/powerforecast/. It starts the engine (powerforecast-api, port 8020, volume powerforecast-data for the stored forecasts) and Redis (powerforecast-redis, volume powerforecast-redis-data):
| Variable | Default | Meaning |
|---|---|---|
PORT | 8020 | engine port |
REDIS_URL | redis://redis:6379/0 | job store |
RUN_TTL_SECONDS | 3600 | how long finished jobs stay in Redis (the stored run stays for good) |
FORECAST_DB_PATH | /data/powerforecast.db | the stored forecasts (SQLite) |
ORCHESTRATOR_URL | empty | PowerOrchestrator, to fetch expired jobs |
FORECAST_N_JOBS | 1 | StatsForecast parallelism (keep 1 in Docker) |
CORS_ORIGINS | * | allowed origins |
NIXTLA_ID_AS_COL | 1 | StatsForecast output format |
docker-compose up -d
docker-compose ps
curl http://localhost:8020/health # {"status":"healthy",…}
docker exec powerforecast-redis redis-cli ping # PONG
Updating the engine keeps the stored forecasts (volume powerforecast-data). The first start of the release with stored forecasts moves the finished jobs still in Redis into the database; older runs had already expired, and the moved ones have no input history (Results shows them without their past).
Backups (SQLite online backup; a plain copy of a live database can be inconsistent):
mkdir -p /root/backups
docker run --rm -v powerforecast-data:/data -v /root/backups:/backup alpine sh -c \
"apk add --no-cache sqlite >/dev/null && sqlite3 /data/powerforecast.db \".backup '/backup/powerforecast-$(date +%Y%m%d-%H%M%S).db'\""
No sign-in check on the API yet. Anyone who can reach
https://forecast-api.planningpowertools.comcan list, change and remove stored forecasts. Until the product has its own authentication, keep regular backups.
Memory: plan about 1 GB for the engine. A forecast of many series runs for minutes (see the Developer Guide, Speed).
2. The wizard and the documentation
The wizard is static. Copy forecast-wizard/ to /var/www/powerforecast/ and Documentation/html/ to /var/www/powerforecast/documentation/html/ (the sidebar's Documentation link opens /documentation/html/index.html). Check forecast-wizard/config.json:
{ "engineApiUrl": "https://forecast-api.planningpowertools.com/api/powerforecast/v1",
"sqliteApiUrl": "https://admin-api.planningpowertools.com/api" }
3. Nginx and HTTPS
Point the two DNS names (A records) at the server before asking for certificates.
/etc/nginx/http.d/forecast-api.conf:
server {
listen 80;
server_name forecast-api.planningpowertools.com;
location /.well-known/acme-challenge/ { root /var/www/certbot; }
location / {
proxy_pass http://localhost:8020;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 600s;
proxy_send_timeout 600s;
proxy_read_timeout 600s; # POST /forecast on many series is long; the wizard uses /jobs
}
}
/etc/nginx/http.d/forecast.conf:
server {
listen 80;
server_name forecast.planningpowertools.com;
root /var/www/powerforecast;
index index.html;
location /.well-known/acme-challenge/ { root /var/www/certbot; }
location / { try_files $uri $uri/ =404; }
}
mkdir -p /var/www/certbot
nginx -t && service nginx reload
certbot --nginx -d forecast-api.planningpowertools.com -d forecast.planningpowertools.com
Certbot adds the HTTPS blocks and the redirects. Renewal: certbot renew --dry-run once, then a daily cron (0 3 * * * certbot renew --quiet --deploy-hook "service nginx reload").
CORS is answered by the engine (CORS_ORIGINS); do not add CORS headers in Nginx as well, or browsers see them twice and refuse.
4. Firewall
Open 22, 80 and 443 only (Hetzner Cloud Firewall or iptables). 8020 and 6380 stay closed: Nginx reaches the engine on localhost.
5. Checks after an install or an update
curl https://forecast-api.planningpowertools.com/health
curl -s https://forecast-api.planningpowertools.com/api/powerforecast/v1/models | head -c 300
Then in a browser: sign in at https://forecast.planningpowertools.com, load forecast-demos/01-retail-demand-monthly/data.csv on step 2, run, and open Results.
Troubleshooting
| Symptom | Look at |
|---|---|
| The wizard says the template cannot be loaded | PowerAdmin reachable? sqliteApiUrl in config.json |
| Run Forecast fails at once | engineApiUrl; curl …/health; docker-compose logs forecast-api |
| A job stays Forecasting for very long | many series: expected (minutes); docker stats for CPU / memory |
Job not found on Results after a while | an engine older than stored forecasts: redeploy; otherwise open the run from Forecasts |
| Forecasts page: "does not keep forecasts yet" | the engine image predates stored forecasts: rebuild and redeploy forecast-api |
| CORS error in the browser | CORS headers added twice (Nginx and engine), or a wrong origin |
| Container restarts | docker-compose logs; memory (dmesg | grep -i kill) |
Quick reference
cd /root/powerforecast
docker-compose ps | logs -f forecast-api | restart forecast-api
docker-compose down && docker-compose up -d # after loading a new image
docker exec powerforecast-redis redis-cli --scan --pattern 'powerforecast:job:*' | head