Skip to content

Heimdall

Heimdall is a start page for your services: tiles you arrange yourself, with optional per-service status. One container, and a SQLite database inside its configuration directory.

Last run on a host 8 Nov 2026 · data restored from a backup

Pinned at 2.8.4 (released 6 Nov 2026) · the newest upstream release

To try it: Docker, nothing else. See Try it locally.

To deploy it behind the proxy: Traefik running and a domain.

On your own machine, with no proxy, domain or certificate. From apps/heimdall/:

Terminal window
cp .env.local.example .env.local
docker compose -f docker-compose.local.yml --env-file .env.local up -d

Then open http://localhost:8080. Stop it again with the same command and down.

From apps/heimdall/:

  1. Copy the environment file and set APP_TRAEFIK_HOST, TZ and PUID/PGID — the last two have to match the host user that owns config/:

    Terminal window
    cp .env.example .env

    Then set APP_BASIC_AUTH_USERS. The route ships with a basic-auth gate, because Heimdall’s user accounts are off until you create them; the placeholder is not a valid hash. Every $ in the hash is doubled for Compose — the command does it, paste the whole line:

    Terminal window
    htpasswd -nbB ops 'the-password' | sed -e 's/\$/\$\$/g'
  2. Create the configuration directory and hand it to that user:

    Terminal window
    mkdir -p config
    sudo chown -R 1000:1000 config
  3. Start it. The first boot takes around thirty seconds while the database is created:

    Terminal window
    docker compose up -d
    docker compose logs heimdall-app --follow
  4. Open https://<APP_TRAEFIK_HOST> and pass the basic-auth prompt. Heimdall has no account at first — everything else, from tiles to accounts, is configured in the interface.

Terminal window
docker compose ps # one service, healthy
curl -sSI https://<APP_TRAEFIK_HOST>/ # 401 — the gate asks
curl -fsSI -u ops https://<APP_TRAEFIK_HOST>/ # asks for the password; 200 with it

Then add one tile and reload. A dashboard that keeps the tile confirms the configuration directory is writable, which the healthcheck does not cover.

ALLOW_INTERNAL_REQUESTS is false, so the status widgets cannot probe private addresses. Setting it to true lets a tile show whether the service behind it is up — and lets the container reach anything on your internal network, which is the trade in the other direction.

Until you create a user, the dashboard is a single shared view. Accounts are added in the interface under Users, not through the environment file.

Database SQLite inside config/ — no server, no dump hook
State config/ — tiles, uploaded icons, users
Quiescing Required. Copying a live SQLite file can capture a torn state — stop the container or snapshot the filesystem.

For borgmatic this stack is source_directories only, with quiescing. There is no database hook.

Small enough to skip, and entirely hand-made. Nothing regenerates a curated dashboard, so what a loss costs is the hour it took to build.

  1. Read the release notes.

  2. Back up config/ with the container stopped.

  3. Raise APP_TAG in .env, then:

    Terminal window
    docker compose pull && docker compose up -d
  4. Load the dashboard. A database migration surfaces there rather than in the log.

Icon uploads fail with a permission error. config/ is not owned by the PUID/PGID from .env. Re-run the chown from step 2.

Adding user: to the service breaks startup. The image starts as root to set up its process supervisor and drops privileges itself. The blueprint leaves the directive off for that reason.

Status widgets show nothing for internal services. ALLOW_INTERNAL_REQUESTS is false. See above before changing it.