9.8 KiB
Invoice Ninja
Type: Invoicing & Billing Application Domain: https://invoice.hyperthrive.io Version: 5.12.28 Architecture: Official Docker stack from invoiceninja/dockerfiles Deployment Date: 2025-10-14
Architecture
Invoice Ninja runs using the official Docker Compose stack with three services:
- invoiceninja-server-1 - Nginx web server (port 80 internal)
- invoiceninja-app-1 - Laravel application with supervisord (PHP-FPM, queue workers, scheduler)
- invoiceninja-db-1 - MariaDB 10.11 database
Traefik Integration
The nginx service connects to the external traefik network and uses labels for automatic SSL/routing:
- Domain: invoice.hyperthrive.io
- Protocol: HTTPS via Let's Encrypt
- Entry Point: websecure (443)
- Internal Port: 80 (nginx)
Directory Structure (Server)
~/services/invoiceninja/
├── docker-compose.yml # Main compose file
├── env # Environment configuration
├── config/
│ └── nginx/
│ └── in-vhost.conf # Nginx vhost config
├── docker/
│ ├── app/
│ │ ├── public/ # Application public files (shared with nginx)
│ │ └── storage/ # Application storage (logs, cache, uploads)
│ └── mysql/
│ └── data/ # Database files
Important: The docker/app/ directories must be owned by user 1500:1500 (the container's runtime user).
Common Operations
Check Status
ssh jared@15.204.247.153 "docker ps --filter name=invoiceninja"
View Logs
# Application logs
ssh jared@15.204.247.153 "docker logs invoiceninja-app-1 --tail 50"
# Database logs
ssh jared@15.204.247.153 "docker logs invoiceninja-db-1 --tail 50"
# Nginx logs
ssh jared@15.204.247.153 "docker logs invoiceninja-server-1 --tail 50"
Update Invoice Ninja
ssh jared@15.204.247.153 "cd ~/services/invoiceninja && \
docker compose pull && \
docker compose up -d && \
docker exec invoiceninja-app-1 php artisan migrate --force && \
docker exec invoiceninja-app-1 php artisan optimize"
Backup Database
ssh jared@15.204.247.153 "cd ~/services/invoiceninja && \
docker compose exec -T db sh -c 'exec mysqldump --all-databases -uroot -p\"\$MYSQL_ROOT_PASSWORD\"' \
> ~/backups/invoiceninja-$(date +%Y%m%d-%H%M%S).sql"
Restore Database
ssh jared@15.204.247.153 "cd ~/services/invoiceninja && \
docker compose exec -T db sh -c 'exec mysql -uroot -p\"\$MYSQL_ROOT_PASSWORD\"' \
< ~/backups/invoiceninja-TIMESTAMP.sql"
Restart Services
ssh jared@15.204.247.153 "cd ~/services/invoiceninja && docker compose restart"
Run Artisan Commands
# Generic pattern
ssh jared@15.204.247.153 "docker exec invoiceninja-app-1 php artisan <command>"
# Examples
ssh jared@15.204.247.153 "docker exec invoiceninja-app-1 php artisan queue:work"
ssh jared@15.204.247.153 "docker exec invoiceninja-app-1 php artisan cache:clear"
ssh jared@15.204.247.153 "docker exec invoiceninja-app-1 php artisan db:seed"
Key Configuration
Environment Variables (env file)
Critical settings in ~/services/invoiceninja/env:
APP_URL=https://invoice.hyperthrive.io- Public URLAPP_KEY=base64:...- Encryption key (DO NOT change after initial setup)DB_HOST=db- Database hostname (container name)MYSQL_ROOT_PASSWORD- DB root passwordMYSQL_DATABASE=ninja- Database nameMYSQL_USER=ninja- DB userMYSQL_PASSWORD- DB passwordTRUSTED_PROXIES=*- Trust Traefik proxyMAIL_MAILER=log- Mail driver (currently logging only)
Traefik Labels (docker-compose.yml)
labels:
- "traefik.enable=true"
- "traefik.http.routers.invoiceninja.rule=Host(`invoice.hyperthrive.io`)"
- "traefik.http.routers.invoiceninja.entrypoints=websecure"
- "traefik.http.routers.invoiceninja.tls.certresolver=letsencrypt"
- "traefik.http.services.invoiceninja.loadbalancer.server.port=80"
- "traefik.docker.network=traefik"
Troubleshooting
Permission Denied Errors
If the app container fails with "Permission denied" creating directories:
ssh jared@15.204.247.153 "cd ~/services/invoiceninja && \
docker compose down && \
sudo chown -R 1500:1500 docker/app && \
docker compose up -d"
Database Connection Issues
Check database is healthy:
ssh jared@15.204.247.153 "docker inspect invoiceninja-db-1 --format='{{.State.Health.Status}}'"
Should return healthy. If not, check logs.
502 Bad Gateway
Usually means the app container isn't running or PHP-FPM hasn't started:
- Check app container status:
docker ps --filter name=invoiceninja-app-1 - Check app logs:
docker logs invoiceninja-app-1 --tail 100 - Look for PHP-FPM startup message: "ready to handle connections"
SSL Certificate Issues
Traefik automatically handles SSL. If certificate issues occur:
- Check Traefik logs:
docker logs traefik - Verify DNS points to server:
dig invoice.hyperthrive.io - Check Traefik dashboard for router/certificate status
Queue Not Processing
The official stack includes queue workers via supervisord. Check they're running:
ssh jared@15.204.247.153 "docker exec invoiceninja-app-1 supervisorctl status"
Should show queue-worker_00 and queue-worker_01 as RUNNING.
Client Portal 500 Errors on Invoice View (pdf_variables bug — IN 5.13.22)
Symptom: All clients get HTTP 500 when viewing invoices via the client portal.
Laravel log signature (check docker/app/storage/logs/laravel.log or docker logs invoiceninja-app-1):
Undefined property: stdClass::$product_columns in PdfSlot.php:149
Root cause: The pdf_variables field in company settings was never properly initialized (left as empty object {}). PdfSlot.php crashes when it tries to access named columns on the empty object. This is an unreported upstream bug in Invoice Ninja 5.13.22 with no upstream fix as of 2026-05-15.
Fix — run once to populate the defaults:
docker exec invoiceninja-app-1 php artisan tinker --execute="
\$company = App\Models\Company::first();
\$settings = \$company->settings;
\$defaults = App\DataMapper\CompanySettings::getEntityVariableDefaults();
\$settings->pdf_variables = (object) \$defaults;
\$company->settings = \$settings;
\$company->save();
echo 'Done: ' . json_encode(\$company->fresh()->settings->pdf_variables->product_columns);
"
A successful run prints the populated product_columns array. No restart required — the fix takes effect immediately.
MariaDB Health Check Unhealthy
Symptom: docker ps shows invoiceninja-db-1 with status (unhealthy).
Root cause: The healthcheck user's password in the container's .my-healthcheck.cnf is out of sync with the actual MariaDB user password (can happen after env changes or manual password resets).
Fix — sync the password:
docker exec invoiceninja-db-1 mariadb -uroot -p<ROOT_PASS> -e \
"ALTER USER 'healthcheck'@'localhost' IDENTIFIED BY '<password_from_.my-healthcheck.cnf>'"
Replace <ROOT_PASS> with the value of MYSQL_ROOT_PASSWORD from ~/services/invoiceninja/env, and <password_from_.my-healthcheck.cnf> with the password found in that file inside the container:
docker exec invoiceninja-db-1 cat /root/.my-healthcheck.cnf
After running the ALTER USER, the container transitions to healthy within one health-check interval (typically 30 s).
Duplicate Stripe Payment Gateway
Symptom: Two Stripe gateways appear in Settings > Payment Gateways, or payments route through an old/invalid gateway config.
Fix: Delete the older/unused gateway entry from the Invoice Ninja admin UI (Settings > Payment Gateways → delete). Keep only the active gateway.
Also check: After any gateway cleanup, verify accepted_credit_cards is non-zero. A value of 0 means no card types are accepted; set it to 7 (Visa + Mastercard + Amex) or the desired bitmask via Settings > Payment Gateways > Edit.
Migration History
This deployment uses the official Invoice Ninja Docker stack (as of 2025-10-14). Previous custom stack was replaced to align with upstream recommendations and simplify maintenance.
Pre-migration backup location: ~/backups/invoiceninja-pre-official/ on server (retained until deployment proven stable)
Incidents
2026-05-15 — Client Portal 500 / MariaDB Unhealthy / Duplicate Gateway
Symptoms observed:
- All clients received HTTP 500 on invoice view in the client portal
invoiceninja-db-1had been showing(unhealthy)for 29 days- Two Stripe payment gateways were present;
accepted_credit_cardswas0
Root causes & fixes:
| Issue | Root cause | Fix applied |
|---|---|---|
| Portal 500 errors | pdf_variables field never initialized ({}); PdfSlot.php:149 crashes on ->product_columns |
Populated defaults via tinker (see troubleshooting section above) |
| MariaDB unhealthy | Healthcheck user password out of sync with .my-healthcheck.cnf |
ALTER USER 'healthcheck'@'localhost' IDENTIFIED BY '...' |
| Duplicate Stripe gateway | Old gateway left over from previous config | Deleted stale gateway from admin UI |
accepted_credit_cards: 0 |
Never set after initial setup | Set to 7 (Visa + Mastercard + Amex) in gateway settings |
Notes:
- The
pdf_variablesbug is unreported upstream as of 2026-05-15 (Invoice Ninja 5.13.22). Watch future releases for a permanent fix. - The MariaDB health check had been broken for 29 days before discovery — consider adding an external health alert for this container.
References
- Official Docker Repository: https://github.com/invoiceninja/dockerfiles
- Invoice Ninja Docs: https://invoiceninja.github.io
- Server Profile:
profile.yaml - Configuration Template:
configs/invoiceninja-official-template.yml