Skip to content
Annuva
Documentation · Installation

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:

yaml
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.com

The container runs as the user node (uid 1000); the data folder has to be writable for it:

bash
mkdir -p data && sudo chown 1000:1000 data
docker compose up -d

On the first start Annuva prints a setup token to the log:

bash
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:

caddyfile
annuva.example.com {
    reverse_proxy localhost:40100
}

Example for nginx:

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:

yaml
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-me

Instead 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

bash
docker run -d --name annuva --restart unless-stopped \
    -p 40100:40100 \
    -v "$(pwd)/data:/var/www/data" \
    ghcr.io/dasred/annuva-app:latest

Health check

The container reports its state at /api/health (database connection and version); Docker uses it as health check.

Edit this page on GitHub