Appearance
Deployment Reference
How it deploys
Every push to master triggers the Gitea Actions pipeline (.gitea/workflows/deploy.yml):
- Builds the Docker image (Dockerfile — multi-stage: Node 24 frontend build + .NET 10 backend build + runtime)
- Pushes to
gitea.apimeld.com/meld/apimeld-scheduler:latest - Deploys via Portainer API using
.gitea/portainer-stack.ymlas the stack definition - Cleans up stale SHA-tagged images from the Gitea registry
The running stack definition lives in .gitea/portainer-stack.yml. Edit that file and push to change any runtime environment variable.
Environment variables
Database (required)
| Variable | Description |
|---|---|
SETUP_DB_HOST | PostgreSQL hostname or IP |
SETUP_DB_PORT | PostgreSQL port (default: 5432) |
SETUP_DB_NAME | Database name |
SETUP_DB_USERNAME | Database user |
SETUP_DB_PASSWORD | Database password |
First-run admin account (required for automated setup)
| Variable | Description |
|---|---|
SETUP_ADMIN_EMAIL | Admin account email |
SETUP_ADMIN_PASSWORD | Admin account password |
SETUP_ADMIN_DISPLAY_NAME | Display name (defaults to email if omitted) |
If these are present on first startup, the setup wizard is skipped and the app configures itself automatically.
Application
| Variable | Example | Description |
|---|---|---|
ASPNETCORE_ENVIRONMENT | Production | Set to Development to enable Swagger UI and detailed errors |
App__BaseUrl | https://poc.apimeld.com | Public-facing URL — used in email links and CORS |
CORS__AdditionalOrigins | http://localhost:6274 | Comma-separated extra origins allowed by CORS (e.g. MCP Inspector) |
Networking — choose one mode
Mode 1: Reverse proxy (NginxPM, Traefik, Cloudflare Tunnel, etc.) — current production setup
The container serves plain HTTP on port 8080. The proxy handles TLS termination and forwards X-Forwarded-For / X-Forwarded-Proto.
| Variable | Value | Description |
|---|---|---|
PROXY_ENABLED | true | Trust X-Forwarded-* headers from the upstream proxy |
NginxPM config: point proxy host to http://<docker-host-ip>:8080. Enable "Force SSL" and "HTTP/2 Support" in NPM. Set HSTS on the NPM proxy host (not on the container).
yaml
# portainer-stack.yml snippet
ports:
- "8080:8080"
environment:
- PROXY_ENABLED=trueMode 2: Direct TLS (cert mounted into container — no proxy)
The container terminates TLS itself on port 8443. Mount a directory containing your certificate.
| Variable | Example | Description |
|---|---|---|
TLS_ENABLED | true | Enable Kestrel HTTPS |
TLS_CERT_PATH | /certs/cert.pem | Path to certificate file inside the container |
TLS_KEY_PATH | /certs/key.pem | Path to private key (PEM only — omit for PFX) |
TLS_CERT_PASSWORD | secret | PFX password (optional, omit if unprotected) |
TLS_PORT | 8443 | HTTPS port (default: 8443) |
Do not set PROXY_ENABLED=true at the same time — these modes are mutually exclusive. HSTS is set automatically by the app in this mode.
PEM example:
yaml
ports:
- "8443:8443"
volumes:
- /host/path/to/certs:/certs
environment:
- TLS_ENABLED=true
- TLS_CERT_PATH=/certs/cert.pem
- TLS_KEY_PATH=/certs/key.pemPFX example:
yaml
ports:
- "8443:8443"
volumes:
- /host/path/to/certs:/certs
environment:
- TLS_ENABLED=true
- TLS_CERT_PATH=/certs/cert.pfx
- TLS_CERT_PASSWORD=your-pfx-passwordLet's Encrypt with Certbot (PEM): Certbot writes to /etc/letsencrypt/live/<domain>/. Mount that directory:
-v /etc/letsencrypt/live/yourdomain.com:/certs:ro
-e TLS_CERT_PATH=/certs/fullchain.pem
-e TLS_KEY_PATH=/certs/privkey.pemNote: Certbot rotates certs — set up a renewal hook that restarts the container after renewal.
Security headers
The following headers are set on every response by the application:
| Header | Value | Purpose |
|---|---|---|
X-Content-Type-Options | nosniff | Prevent MIME-type sniffing |
X-Frame-Options | DENY | Block clickjacking via iframes |
X-XSS-Protection | 0 | Disable legacy browser XSS auditor (CSP replaces it) |
Referrer-Policy | strict-origin-when-cross-origin | Limit referrer leakage |
Permissions-Policy | camera=(), microphone=(), geolocation=(), payment=() | Opt out of unused browser APIs |
Strict-Transport-Security | max-age=31536000; includeSubDomains | Direct TLS mode only |
HSTS in proxy mode: Enable HSTS on the NginxPM proxy host instead (Advanced tab → Custom Nginx config, or the HSTS toggle). Do not set it on the container — Kestrel serves plain HTTP on the internal leg.
CSP (Content-Security-Policy): Not yet configured. Monaco Editor requires unsafe-eval for its web workers; this needs per-route tuning before enforcement.
Volumes
| Mount path | Purpose |
|---|---|
/app/config | Runtime config overrides (appsettings.Production.json written by setup wizard). Persist this across container restarts. |
/certs | (Direct TLS mode only) Certificate directory. Read-only mount recommended. |
Ports
| Port | Protocol | When active |
|---|---|---|
8080 | HTTP | Always |
8443 | HTTPS | Only when TLS_ENABLED=true |