Skip to main content

Docker Installation (Recommended)

The fastest way to run Invoicerr is using Docker Compose. A prebuilt image is available at ghcr.io/invoicerr-app/invoicerr.

Supported architectures​

  • linux/amd64 (x86_64)
  • linux/arm64/v8 (ARMv8)
warning

linux/arm/v7 is published but untested CI does build and publish a linux/arm/v7 entry in the image's multi-arch manifest, so docker pull will hand a 32-bit ARM host an image that claims to fit it. Whether the application actually runs there has never been established on real hardware, and Prisma's own prebuilt engine coverage for that architecture is the doubt behind this warning. Treat it as unsupported until someone runs it on a 32-bit ARM device and reports back; linux/amd64 and linux/arm64/v8 are the two variants this project exercises.

Quick start​

  1. Create a docker-compose.yml file with the following content, then adjust the environment variables to your setup:

    docker-compose.yml
    services:
    invoicerr:
    image: ghcr.io/invoicerr-app/invoicerr:latest
    ports:
    - "80:80"
    environment:
    - DATABASE_URL=postgresql://invoicerr:invoicerr@invoicerr_db:5432/invoicerr_db
    - APP_URL=https://invoicerr.example.com
    - CORS_ORIGINS=http://localhost:5173,https://invoicerr.example.com

    # Redis — required (BullMQ queues the document-send jobs). No password on a
    # single-host Docker network by default; set REDIS_PASSWORD on both services to add one.
    - REDIS_HOST=redis
    - REDIS_PORT=6379

    # Email delivery - see "Email delivery" below for the Resend alternative (Brevo's own SMTP
    # relay also works with the SMTP settings below)
    - SMTP_HOST=smtp-relay.example.com
    - SMTP_USER="username@example.com"
    - SMTP_FROM="user-from@example.com"
    - SMTP_PASSWORD="your_smtp_password"
    - SMTP_PORT=587
    - SMTP_SECURE=false

    - BETTER_AUTH_SECRET="your_better_auth_secret"
    depends_on:
    - invoicerr_db
    - redis

    invoicerr_db:
    image: postgres:15
    environment:
    POSTGRES_USER: invoicerr
    POSTGRES_PASSWORD: invoicerr
    POSTGRES_DB: invoicerr_db
    volumes:
    - db_data:/var/lib/postgresql/data

    redis:
    image: bitnami/redis:latest
    environment:
    ALLOW_EMPTY_PASSWORD: "yes"
    volumes:
    - redis_data:/bitnami

    volumes:
    db_data:
    driver: local
    redis_data:
    driver: local

    The app will not boot at all without a reachable Redis — it is not optional, and there is no degraded fallback if it is missing.

  2. Run the app:

    docker compose up -d
  3. Open your browser at http://localhost.

tip

The repository's docker-compose.yml also includes an OIDC example (placeholder values, live rather than commented out — set OIDC_CLIENT_ID to actually register the provider, or remove the whole block if you don't want single sign-on).

Updating​

docker compose pull && docker compose up -d picks up a new image and re-applies pending database migrations automatically on the container's next boot — same as any other restart.

One thing that is not automatic: if a release actually drops a country from one of the document-action policy / identifier-requirements / B2G-routing catalogs, removing that country's database rows is a deliberate, separate step — every automatic boot path only ever adds/updates rows, on purpose (so a rolling multi-container setup, or simply restarting on a half-pulled image, can never delete a country a newer container already seeded). Run this once, after the new container is up, whenever a release note says a country was removed:

docker compose exec invoicerr sh -c "cd /usr/share/nginx/backend && npm run catalogs:release"

(swap invoicerr for your own service name if you renamed it in docker-compose.yml). It is idempotent — safe to run on every update regardless of whether that particular release actually removed a country.

Environment variables​

These are set under the invoicerr service's environment key.

VariableDescription
DATABASE_URLPostgreSQL connection string, e.g. postgresql://invoicerr:invoicerr@invoicerr_db:5432/invoicerr_db
APP_URLFull public URL of the frontend (e.g. https://invoicerr.example.com). Required for email templates and links, and used as better-auth's own base URL.
REDIS_HOST / REDIS_PORT / REDIS_PASSWORDRequired — the app refuses to boot without a reachable Redis (BullMQ queues every document-send job). Defaults to localhost:6379, no password, if unset; REDIS_URL (e.g. redis://:pass@redis:6379) is accepted instead and wins when both are set.
BETTER_AUTH_SECRETThe one that's actually documented and recommended — any random string, e.g. openssl rand -hex 32. Leaving it (and JWT_SECRET below) unset falls back to a known default, which the app flags as insecure.
JWT_SECRETA legacy fallback only — better-auth reads BETTER_AUTH_SECRET || JWT_SECRET. Set BETTER_AUTH_SECRET instead for anything new; this one exists so an older deployment that only ever set JWT_SECRET keeps working unchanged.
DEFAULT_LOCALEOptional. Instance-wide fallback language (en, fr, it, pl, de, or pt) for any document or system email whose client/company/user never set one of their own — see Document Language for the full cascade. Left unset, everything renders in English exactly as before.

Make sure port 80 is available on your host machine, or change the port mapping.

Email delivery​

Invoicerr needs to send emails (quote/invoice notifications, signature links). This is configured on two levels:

  • Instance level — the MAIL_PROVIDER/SMTP_*/RESEND_API_KEY variables below, set once for the whole deployment. Choose one provider with MAIL_PROVIDER, or leave it unset to auto-detect: RESEND_API_KEY present selects Resend (it wins even if SMTP_HOST is also set), otherwise SMTP is used.
  • Company level — each company can set its own mail server in Settings → Mail (SMTP or Resend). A company's own server, when set, is used for everything that company sends, instead of the instance-level one below; a company that never configures one keeps using the instance level. A send is refused (never silently dropped) only if neither level is configured.
- MAIL_PROVIDER=smtp # can be omitted, this is the default
- SMTP_HOST=smtp-relay.example.com
- SMTP_USER="username@example.com"
- SMTP_FROM="user-from@example.com" # optional, defaults to SMTP_USER
- SMTP_PASSWORD="your_smtp_password"
- SMTP_PORT=587 # default SMTP port for TLS
- SMTP_SECURE=false # set to true if your SMTP server requires a secure connection
VariableDescription
SMTP_HOST, SMTP_USER, SMTP_PASSWORDCredentials and server used for sending emails
SMTP_FROMOptional — sender address. Defaults to SMTP_USER if omitted
SMTP_PORTSMTP port (default 587)
SMTP_SECURESet to true if your SMTP server requires a secure connection
info

Brevo (formerly Sendinblue) has no dedicated provider any more — use its own SMTP relay (smtp-relay.brevo.com) with this SMTP option instead.

Instance operators​

Most actions in Invoicerr are scoped to one company. A small number of actions are instance-wide instead — they act across every company on this deployment at once. Today there is exactly one: resetting the whole instance (Settings → Danger Zone → "Reset instance"), which permanently deletes every company, user and document, then signs everyone out.

Nobody can do this by default. Set INSTANCE_OPERATOR_EMAILS to a comma-separated (case-insensitive) list of e-mail addresses to name who can:

- INSTANCE_OPERATOR_EMAILS=you@example.com,co-admin@example.com

Leaving it unset means no instance operator exists at all — the reset screen never appears, and the underlying API routes refuse every caller.

The instance-reset feature itself is unavailable entirely on a hosted (SaaS) deployment, regardless of this variable — the screen and its routes are hidden as if they did not exist, not merely forbidden.

The SAME INSTANCE_OPERATOR_EMAILS list is also used by one other, unrelated route: GET /api/backup/status (instance file-backup status), which used to trust any company's OWNER and now requires a real instance operator instead — on self-hosted and on a hosted deployment alike (unlike the reset feature, backup status is not hidden on SaaS).

danger

An instance reset is irreversible and cannot be scoped to "just one company" — it takes down every company on the deployment. It still requires a fresh e-mailed confirmation code and typing "RESET INSTANCE" exactly, but there is no undo once it runs.