Getting started
Installation
Run Annuva with Docker Compose, put a reverse proxy in front and choose a database.
Requirements
- A server or computer with Docker and Docker Compose
- For access from the internet: a domain and a reverse proxy with TLS (e.g. Nginx Proxy Manager, Caddy, Traefik or nginx)
The image ghcr.io/dasred/annuva-app:latest is built for linux/amd64 and linux/arm64, so it also runs on a Raspberry Pi or an ARM server.
Quick start with Docker Compose
Create a folder, save the following file as compose.yaml and adjust it if you like:
services:
annuva:
image: ghcr.io/dasred/annuva-app:latest
container_name: annuva
restart: unless-stopped
ports:
- "40100:40100"
volumes:
- ./data:/var/www/data
environment:
# Optional – see "Configuration"
DEFAULT_LOCALE: en
SMTP_HOST: smtp.example.com
SMTP_USER: annuva@example.com
SMTP_PASSWORD: secret
MAIL_FROM: annuva@example.comThe container runs as the user node (uid 1000); the data folder has to be writable for it:
mkdir -p data && sudo chown 1000:1000 data
docker compose up -dOn the first start Annuva prints a setup token to the log:
docker compose logs annuva | grep -A1 "Setup required"Open http://localhost:40100 (or your server's address), enter the token and follow the setup wizard.
Reverse proxy and TLS
For anything beyond localhost, put a reverse proxy with TLS in front of the container and forward your domain to port 40100. Annuva takes its public URL from the request headers X-Forwarded-Host and X-Forwarded-Proto, so it works under any domain without configuration.
Example for Caddy:
annuva.example.com {
reverse_proxy localhost:40100
}Example for nginx:
server {
listen 443 ssl http2;
server_name annuva.example.com;
location / {
proxy_pass http://127.0.0.1:40100;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 25m;
}
}With Nginx Proxy Manager create a proxy host for your domain pointing to http://<server>:40100, enable Force SSL and HTTP/2 and request a Let's Encrypt certificate.
Increase the upload limit of your proxy if you want to upload receipts or offers larger than its default (Annuva itself accepts 20 MB, see
UPLOAD_MAX_SIZE).
MySQL or PostgreSQL
SQLite is perfect for a single freelancer. If you prefer a database server, set DB_DRIVER and the connection – for example with PostgreSQL:
services:
annuva:
image: ghcr.io/dasred/annuva-app:latest
restart: unless-stopped
ports:
- "40100:40100"
volumes:
- ./data:/var/www/data
environment:
DB_DRIVER: postgres
DB_HOST: db
DB_NAME: annuva
DB_USER: annuva
DB_PASSWORD: change-me
depends_on:
- db
db:
image: postgres:17-alpine
restart: unless-stopped
volumes:
- ./postgres:/var/lib/postgresql/data
environment:
POSTGRES_DB: annuva
POSTGRES_USER: annuva
POSTGRES_PASSWORD: change-meInstead of the single DB_* values you can also pass a DATABASE_URL such as postgres://annuva:change-me@db/annuva. The data folder is still needed for uploaded files, invoice PDFs and generated secrets.
Without Compose
docker run -d --name annuva --restart unless-stopped \
-p 40100:40100 \
-v "$(pwd)/data:/var/www/data" \
ghcr.io/dasred/annuva-app:latestHealth check
The container reports its state at /api/health (database connection and version); Docker uses it as health check.