Docs

Install with Docker Compose

You need Docker with Compose v2. On Windows and macOS, Docker Desktop works.

git clone https://github.com/aubergine-ux/axlebook.git
cd axlebook
docker compose up -d

Open http://localhost:8080 and create your account. Everything you enter is kept in theaxlebook-data volume. To look around first, put SEED_DEMO_DATA=true in a.env file next to docker-compose.yml before the first start: you get a 2015 Tacoma and a 2018 Civic with a couple of years of history.

Using the published image

Images for linux/amd64 and linux/arm64 are published to GitHub Container Registry with each release.

services:
  axlebook:
    image: ghcr.io/aubergine-ux/axlebook:latest
    restart: unless-stopped
    ports:
      - "8080:8080"
    environment:
      BASE_URL: https://cars.example.com
      TZ: America/Chicago
    volumes:
      - axlebook-data:/data
volumes:
  axlebook-data:

Postgres instead of SQLite

SQLite is plenty for a household. For Postgres, use the bundled overlay or point DATABASE_URL at a server you already run:

POSTGRES_PASSWORD=pick-something docker compose -f docker-compose.yml -f docker-compose.postgres.yml up -d

Optional local AI

The Ollama overlay adds a private model server; the model downloads once (about 2 GB) on first start:

docker compose -f docker-compose.yml -f docker-compose.ai.yml up -d

Configuration

Everything is an environment variable and everything is optional. Put them in a .env file next to docker-compose.yml. This is the app’s.env.example, exactly as it ships:

# Axlebook configuration. Copy to `.env` and adjust; every variable is optional.
# Docker Compose picks up `.env` automatically. Full reference: README.md → Configuration.

# ── Server ───────────────────────────────────────────────────────────────────
# Port inside the container (publish it with AXLEBOOK_PORT in docker-compose.yml).
# PORT=8080
# AXLEBOOK_PORT=8080
# The URL people use to reach Axlebook. Recommended behind a reverse proxy, and
# required for single sign-on. It pins the CSRF origin check and builds links in
# notifications.
# BASE_URL=https://cars.example.com
# Trust X-Forwarded-* headers from a reverse proxy (true/false).
# TRUST_PROXY=false
# fatal | error | warn | info | debug | trace | silent
# LOG_LEVEL=info
# Time zone used to decide what "today" is for reminders, e.g. America/Chicago.
# TZ=UTC

# ── Storage ──────────────────────────────────────────────────────────────────
# Where the SQLite database, photos and receipts live (the Docker volume).
# DATA_DIR=/data
# Leave unset for SQLite. Set a Postgres URL to use Postgres instead.
# DATABASE_URL=postgres://axlebook:secret@db:5432/axlebook
# Largest single upload (photos, receipts, backup restores), in megabytes.
# MAX_UPLOAD_MB=25

# ── Accounts ─────────────────────────────────────────────────────────────────
# Create the first admin without the setup screen (only if nobody can sign in yet).
# ADMIN_USERNAME=admin
# ADMIN_PASSWORD=change-me-to-something-long
# How long a sign-in lasts without being used, in days.
# SESSION_DAYS=30
# Turn off username/password sign-in (when everyone uses single sign-on).
# DISABLE_LOCAL_LOGIN=false

# ── Single sign-on (OIDC: Authentik, Authelia, Keycloak, …) ─────────────────────
# Redirect URI to register with your provider: <BASE_URL>/api/v1/auth/oidc/callback
# OIDC_ISSUER=https://auth.example.com/application/o/axlebook/
# OIDC_CLIENT_ID=axlebook
# OIDC_CLIENT_SECRET=
# OIDC_SCOPES=openid profile email
# Text on the sign-in button: "Continue with …"
# OIDC_LABEL=Single sign-on
# Create an account the first time someone signs in (true/false).
# OIDC_AUTO_CREATE_USERS=true
# Role for those new accounts: admin | editor | viewer
# OIDC_DEFAULT_ROLE=editor
# Members of this group (from the "groups" claim) become admins.
# OIDC_ADMIN_GROUP=axlebook-admins

# ── Email notifications (optional) ─────────────────────────────────────────────
# Needed only for the "Email" notification channel. ntfy, Gotify, Discord and
# webhooks work without it.
# SMTP_HOST=smtp.example.com
# SMTP_PORT=587
# SMTP_SECURE=false
# SMTP_USER=
# SMTP_PASS=
# SMTP_FROM=Axlebook <axlebook@example.com>
# How often reminders are checked for notifications, in minutes.
# REMINDER_SCAN_INTERVAL_MIN=60

# ── Local AI (optional) ────────────────────────────────────────────────────────
# Spreadsheet import help, tailored maintenance schedules, economy estimates and
# model-specific tips, from a model running on your own server through Ollama.
# docker-compose.ai.yml sets this up for you. Leave AI_URL unset to turn it off.
# AI_URL=http://ollama:11434
# Any Ollama model that handles JSON well. Bigger models give better answers.
# AI_MODEL=qwen2.5:3b
# Download the model on first start. Set false for offline installs and pull it yourself.
# AI_PULL=true
# How long to wait for an answer, in seconds (CPU-only machines can be slow).
# AI_TIMEOUT_SEC=300

# ── Defaults ─────────────────────────────────────────────────────────────────
# Unit system for new accounts: imperial | metric
# DEFAULT_UNIT_SYSTEM=imperial
# ISO 4217 currency code for the instance (admins can change it in Settings).
# DEFAULT_CURRENCY=USD
# Interface language for new accounts.
# DEFAULT_LOCALE=en

# ── Demo ─────────────────────────────────────────────────────────────────────
# Fill the garage with a 2015 Tacoma and a 2018 Civic with a few years of history.
# Runs once. Uses the first admin account, or creates "demo" (password from
# ADMIN_PASSWORD, otherwise "axlebook-demo").
# SEED_DEMO_DATA=false

Backups and restore

From the app: Settings, Your data, Download backup. You get a zip with every record, reminder, part, inspection, photo and receipt. Admins get the whole instance; everyone else gets the vehicles they own.

Restoring: on a fresh install with no vehicles yet, an admin uses Settings, Your data, Restore a backup. Accounts are matched by username. A backup restores into SQLite or Postgres, so this is also how you move between them.

From the command line, for scheduled backups of the data directory:

docker compose stop axlebook
docker compose cp axlebook:/data ./axlebook-backup-$(date +%F)
docker compose start axlebook

With Postgres, also run pg_dump against the database.

Upgrading

Take a backup first. Then, if you built from source:

git pull
docker compose up -d --build

Or, with the published image:

docker compose pull
docker compose up -d

The database is upgraded automatically when Axlebook starts. Your data stays in the volume.

Reverse proxy

Set BASE_URL to the address people type (scheme, host and port all count) andTRUST_PROXY=true. Axlebook refuses changes that come from any other origin. Allow uploads at least as large as MAX_UPLOAD_MB (25 MB by default).

Caddy

cars.example.com {
	reverse_proxy localhost:8080
}

nginx

server {
    listen 443 ssl;
    server_name cars.example.com;
    # ssl_certificate and ssl_certificate_key go here
    client_max_body_size 25m;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Traefik

Labels on the axlebook service, with Traefik’s Docker provider and an entrypoint named websecure:

services:
  axlebook:
    labels:
      - traefik.enable=true
      - traefik.http.routers.axlebook.rule=Host(`cars.example.com`)
      - traefik.http.routers.axlebook.entrypoints=websecure
      - traefik.http.routers.axlebook.tls=true
      - traefik.http.services.axlebook.loadbalancer.server.port=8080
    environment:
      BASE_URL: https://cars.example.com
      TRUST_PROXY: "true"

Importing data

  • A spreadsheet you already keep: Garage, Import a spreadsheet. Upload an .xlsx or .csv with one sheet per vehicle or several vehicles in one sheet. Check the columns, units and vehicles it found, fill in anything missing, preview, then import. Rows already in Axlebook are skipped.
  • CSV for one vehicle: on a vehicle’s Records tab, Import CSV. Choose the record type and date order, preview, then import. Axlebook’s own CSV export imports back the same way.
  • From LubeLogger (experimental): export a CSV from LubeLogger and import it the same way; its column names are recognised.
  • Another Axlebook: restore a backup, as above.

Something didn’t import the way you expected? Open an issue with a sample of the file (with personal details removed).