Self-Hosting
Running your own instance of News Dashboard via Docker, Docker Compose, or Helm.
Know your role
A deployment operator chooses a deployment method, supplies secrets and environment configuration, operates PostgreSQL and persistent storage, monitors health, and performs backups and upgrades. An application administrator signs in to manage users and review ingest operations, statistics, and analytics.
One person can hold both roles, but host or cluster access does not grant application administrator access. After deployment, continue with Administration and operations for the in-app controls.
Deployment options
| Option | Best for |
|---|---|
| Docker Compose | Single-node deployments using the published GHCR image. |
| Docker run | Small installations where you already manage PostgreSQL separately. |
| Helm | Kubernetes deployments with bundled or external PostgreSQL. |
For local development, use the root docker-compose.yml. For production, use
docker-compose.prod.yml or Helm; the development compose file contains
insecure local defaults.
Production Compose quick start
-
Copy
.env.exampleto.env. -
Set strong values for
SESSION_SECRET,BOOTSTRAP_ADMIN_USERNAME,BOOTSTRAP_ADMIN_PASSWORD,POSTGRES_PASSWORD, andNEO4J_PASSWORD. -
Start the stack:
docker compose -f docker-compose.prod.yml up -d -
Verify health:
curl http://localhost:8080/api/health
The production Compose stack includes a bundled Neo4j container and wires the
app to it with NEO4J_*, so the knowledge graph is enabled as soon as the
stack is up. Backfill existing entities after first start:
docker compose -f docker-compose.prod.yml exec news-dashboard \
news-dashboard graph-backfill --limit 250 --days 30
docker compose -f docker-compose.prod.yml exec news-dashboard \
news-dashboard graph-relationship-backfill --limit 50 --days 7
The application image is published as:
ghcr.io/lihor-hub/news-dashboard:latest
ghcr.io/lihor-hub/news-dashboard:v<version>
ghcr.io/lihor-hub/news-dashboard:<commit-sha>
ghcr.io/lihor-hub/news-dashboard@sha256:<digest>
Resolve and deploy the published sha256 digest in production instead of
tracking a mutable tag.
Production Helm quick start
The production Helm contract terminates application TLS at the Ingress and keeps the application Service private:
(
set -euo pipefail
IMAGE_DIGEST="${IMAGE_DIGEST:?set IMAGE_DIGEST to sha256:<64 lowercase hex>}"
: "${SESSION_SECRET:?set SESSION_SECRET}"
: "${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}"
: "${POSTGRES_HOST_PATH:?set POSTGRES_HOST_PATH}"
source ./scripts/production-deploy-lib.sh
production_cutover_enabled || { echo "Ingress cutover is not enabled" >&2; exit 2; }
prepare_production_helm_secret_files
helm upgrade --install news-dashboard ./helm/news-dashboard \
--namespace news-dashboard --create-namespace \
--values ./helm/news-dashboard/values-production.yaml \
--set-string image.digest="${IMAGE_DIGEST}" \
--set-string postgresql.persistence.hostPath="$POSTGRES_HOST_PATH" \
--set-file app.auth.sessionSecret="$PRODUCTION_SESSION_SECRET_FILE" \
--set-file postgresql.password="$PRODUCTION_POSTGRES_PASSWORD_FILE"
)
Supply secrets and installation-specific persistence at runtime. Do not commit
them to a values file or pass their values through Helm's --set arguments.
The shared helper uses protected temporary files and removes them on exit.
Private/custom endpoints require a policy-only strict JSON additional-egress
values file. Use deploy/additional-egress-values.example.json as the shape and
persist it through ADDITIONAL_EGRESS_VALUES_FILE for manual deploys or the
production GitHub environment variable ADDITIONAL_EGRESS_VALUES for CI. Never
put credentials in this non-secret NetworkPolicy input. YAML-only syntax,
aliases, merge keys, comments, and multiple documents are rejected.
Before public cutover, verify the ClusterIP Service, TLS Ingress, backups and
restore, rollback revision, and the existing /keycloak route. Caddy cannot
share ports 80 and 443 with the ingress controller.
The live appliance procedure requires human access and is tracked in
issue #1302. Follow
Ingress HTTPS and Caddy migration for the
staged verification and backend-first rollback order. Leave
INGRESS_CUTOVER_ENABLED unset until that procedure is ready; the main workflow
will publish and scan without touching the live release.
Required configuration
News Dashboard uses PostgreSQL at runtime. Configure either DATABASE_URL or
the split POSTGRES_HOST, POSTGRES_PORT, POSTGRES_DB, POSTGRES_USER, and
POSTGRES_PASSWORD variables.
At minimum, a production instance also needs:
| Variable | Purpose |
|---|---|
SESSION_SECRET | Signs sessions and, unless overridden, digest mark-read tokens. |
BOOTSTRAP_ADMIN_USERNAME | First admin username when no users exist. |
BOOTSTRAP_ADMIN_PASSWORD | First admin password when no users exist. |
POSTGRES_PASSWORD | Password for the bundled or configured PostgreSQL user. |
NEO4J_PASSWORD | Password for the bundled Neo4j graph store. |
See Configuration for authentication, HTTPS, backup, and integration guides. The root README Configuration section is the canonical environment-variable reference.
Operations
- Use
/api/livefor liveness checks; it does not require database access. - Use
/api/readyfor readiness checks; it verifies database connectivity. - Use
/api/healthfor load-balancer or manual health checks. - Enable
/metricswithMETRICS_ENABLED=trueonly when you want Prometheus exposition. - Enable
/docs,/redoc, and/openapi.jsonwithENABLE_API_DOCS=trueonly in trusted environments.
To upgrade a Compose deployment, pull the pinned replacement image and restart:
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d
To roll back, set the image tag to the previous known-good version and run the same pull/up commands.