Project burndowns from InvoiceNinja — as a self-hosted Portainer stack.
- Docker + Docker Compose (or Portainer)
- Running InvoiceNinja instance with API access
- InvoiceNinja project Custom Field 2 configured as a date field — used to store the project cancellation/early-end date
- (Optional) Traefik with
traefik_proxynetwork for HTTPS
# 1. Create env file
cp .env.example .env
# → fill .env with your values (see below)
# 2. Start stack
docker compose up -d --build
# 3. Open browser
# http://localhost:6088 (or your domain)| Variable | Required | Description |
|---|---|---|
POSTGRES_PASSWORD |
✅ | Secure password for PostgreSQL |
INVOICENINJA_URL |
✅ | URL of your InvoiceNinja instance |
INVOICENINJA_API_KEY |
✅ | API token from InvoiceNinja → Settings |
DOMAIN |
— | Domain for Traefik (e.g. burndown.example.com) |
SYNC_INTERVAL_MINUTES |
— | Sync interval in minutes (default: 10) |
POSTGRES_DB |
— | DB name (default: burndown) |
POSTGRES_USER |
— | DB user (default: burndown) |
InvoiceNinja API
│
▼
Sync Worker (Node.js) ← runs every N minutes
│
▼
PostgreSQL ←→ Redis (Cache)
│
▼
API (Fastify)
│
▼
Frontend (React + Recharts)
│
▼
Nginx → Browser
| Service | Description |
|---|---|
postgres |
Primary database |
redis |
Cache + job queue |
api |
REST API (Fastify, port 3000 internal) |
worker |
Sync worker (InvoiceNinja → PostgreSQL) |
frontend |
React SPA via Nginx (port 80) |
GET /api/health → Status
GET /api/projects → All projects with stats
GET /api/projects/:id/burndown → Burndown data for a project
POST /api/sync → Trigger manual sync
- Budget:
project.budgeted_hoursfrom InvoiceNinja - Ideal line: Linear from
budgeted_hoursto 0 between start and end date - Actual line:
budgeted_hours - Σ completed time entriescumulated - Corrections: Retroactive changes to time entries affect the entire history (no snapshot locking)
Remove the labels from the frontend service in docker-compose.yml and expose a port:
frontend:
ports:
- "6088:80"
networks:
- internal # remove proxy networkAnd remove the proxy network from the networks section.