8.1 KiB
homarr
Deploy Homarr as a self-contained Docker Compose stack behind Traefik, with seeded admin user, OIDC group and customizable application tiles.
What this role does
- Deploys the official Homarr container with Traefik labels
- Seeds the SQLite database with:
- server settings (locale, analytics, crawling, default board)
- a default board with the three layouts (desktop/tablet/mobile)
- a local admin user with bcrypt-hashed password
- OIDC and credentials admin groups with full permissions
- application tiles defined in
homarr_apps, auto-laid-out across all three screen sizes via the bundledhomarr_compute_layoutsfilter
- Skips the onboarding wizard so the instance is usable right after deploy
- Restarts the container via handler when the seed or compose file changes
What this role does NOT do
- Does not configure OIDC end-to-end — set
homarr_oidc_*variables and configure the corresponding client in your identity provider - Does not migrate existing Homarr databases — only seeds empty ones
- Does not create users beyond the single local admin (OIDC users are provisioned on first login)
Required variables
Provide via OpenBao, Ansible Vault, or extra-vars. Never commit real secrets to version control.
| Variable | Format | Generate with |
|---|---|---|
homarr_secret_encryption_key |
64-char hex string | openssl rand -hex 32 |
homarr_admin_password |
strong password | openssl rand -base64 24 |
homarr_oidc_client_secret |
from your identity provider | — |
The assert task at the top of the role will fail fast if the encryption
key is missing or malformed.
Configurable variables
See defaults/main.yml for the full list. Most useful overrides:
| Variable | Default | Purpose |
|---|---|---|
homarr_domain |
homarr.local.test |
Traefik Host rule |
homarr_base_url |
https://home.local.test |
NEXTAUTH_URL / BASE_URL |
homarr_auth_providers |
credentials |
credentials, oidc, or both |
homarr_oidc_issuer |
empty | Identity provider issuer URL |
homarr_oidc_client_id |
empty | OIDC client id |
homarr_oidc_admin_group |
homarr-admins |
Group granting admin role |
homarr_apps |
[] |
List of application tiles, see below |
Application tiles
homarr_apps is a list of tile definitions that are seeded into the
default board. Each entry needs:
| Field | Required | Description |
|---|---|---|
id |
yes | Unique slug, used as app-<id> and item-<id> |
name |
yes | Display name |
icon |
yes | Icon URL |
href |
yes | Click target |
width |
yes | Tile width in grid cells (1–10) |
description |
no | Tooltip / subtitle |
height |
no | Tile height (default 1) |
The role validates that all id values are unique.
Auto-layout
Tiles are packed left-to-right into three layouts:
- Desktop: 10 columns
- Tablet: 6 columns
- Mobile: 2 columns
When a tile does not fit the remaining width of a row, it wraps to the
next row. Tile width is clamped to the grid width (a tile with
width: 8 becomes width: 6 on tablet and width: 2 on mobile).
Example
homarr_apps:
- id: nextcloud
name: Nextcloud
description: Cloud Storage & Collaboration
icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/nextcloud.png
href: https://cloud.example.com
width: 2
- id: keycloak
name: Keycloak
description: Identity & Access Management
icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/keycloak.png
href: https://auth.example.com
width: 2
Layout filter plugin
The grid-packing algorithm that places tiles on the desktop, tablet
and mobile layouts lives in filter_plugins/homarr_layout.py rather
than inside the Jinja seed template. This keeps the SQL template
readable and lets the algorithm be unit-tested in isolation.
The filter is invoked once from tasks/main.yml:
- name: Compute Homarr app layouts
ansible.builtin.set_fact:
homarr_layout: "{{ homarr_apps | homarr_compute_layouts }}"
This produces a homarr_layout fact with two keys, both consumed by
templates/homarr_seed.sql.j2:
| Key | Shape | Purpose |
|---|---|---|
apps |
list, same order as homarr_apps |
each entry enriched with desktop, tablet, mobile sub-dicts of {x, y, w, h} |
section_height |
dict with desktop, tablet, mobile |
minimum height of the parent section so all tiles fit |
The filter signature accepts custom column counts if Homarr ever changes the breakpoint widths:
{{ homarr_apps | homarr_compute_layouts(desktop_cols=12, tablet_cols=8, mobile_cols=4) }}
To debug a layout without running the full deploy, run the play with
-vv — the Show computed app layouts task dumps the full
homarr_layout fact.
Running the filter tests
The filter is covered by unit tests in
filter_plugins/tests/test_homarr_layout.py:
pip install pytest ansible-core
pytest filter_plugins/tests/
15 tests cover packing, width clamping, height/section-height, input validation and custom grid sizes.
First login
After the role completes, log in at {{ homarr_base_url }} with:
- Username: value of
homarr_admin_username(defaultadmin) - Password: value of
homarr_admin_password
OIDC users are provisioned on first login if their identity provider
group matches homarr_oidc_admin_group. They receive admin permissions
automatically through the seeded group-oidc-admins group.
Example playbook
- name: Deploy Homarr service
hosts: homarr_servers
become: true
roles:
- digitalboard.core.homarr
With inventory variables:
# inventories/<env>/group_vars/homarr_servers.yml
homarr_domain: home.digitalboard.ch
homarr_base_url: "https://home.digitalboard.ch"
homarr_auth_providers: "credentials,oidc"
homarr_oidc_issuer: "https://auth.digitalboard.ch/realms/Digitalboard"
homarr_oidc_client_id: "homarr-digitalboard"
homarr_oidc_client_name: "Digitalboard"
homarr_secret_encryption_key: >-
{{ lookup('community.hashi_vault.vault_kv2_get',
'digitalboard/homarr',
mount_point='kv').data.data.encryption_key }}
homarr_admin_password: >-
{{ lookup('community.hashi_vault.vault_kv2_get',
'digitalboard/homarr',
mount_point='kv').data.data.admin_password }}
homarr_oidc_client_secret: >-
{{ lookup('community.hashi_vault.vault_kv2_get',
'digitalboard/homarr',
mount_point='kv').data.data.oidc_client_secret }}
homarr_apps:
- id: nextcloud
name: Nextcloud
description: Cloud Storage
icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/nextcloud.png
href: https://cloud.digitalboard.ch
width: 2
- id: keycloak
name: Keycloak
description: Identity & Access Management
icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/keycloak.png
href: https://auth.digitalboard.ch
width: 2
Re-running the role
The role is idempotent for the typical re-run case:
- The seed only runs when no local admin user exists in the database
- The compose file and seed template are deployed via
template, which only changes content when the inputs change - The restart handler only fires when one of those templates changes
If you need to re-seed an existing database (for example after deleting the database file to apply schema changes), the role will detect the fresh database and seed it again on the next run.
Troubleshooting
Login fails after deploy. Verify that the bcrypt hash was written correctly:
sqlite3 /srv/data/homarr/homarr/appdata/db/db.sqlite \
"SELECT id, name, email, length(password), provider FROM user;"
Expected: one row with user-local-admin, password length 60,
provider credentials.
Encryption key validation fails. The key must be exactly 64
characters and contain only hex digits ([a-fA-F0-9]). Both upper-
and lowercase are accepted.
App tiles overlap. Check homarr_apps for duplicate id values.
The role validates this, but if you bypass the check, the seed will
still run and Homarr will display only one of the duplicates.