A self-hosted, drag-and-drop web editor for gethomepage's
services.yaml— organize sections and services, edit every field, pick or upload icons, and apply changes to your live dashboard. Runs in one container, behind a sign-in you set up on first boot.
- What it does
- Features
- Requirements
- Install with Docker Compose
- Configuration reference
- The login
- Using the app
- Custom icon uploads & the Homepage restart
- Backups
- Updating
- How icons render
- Release notes
- Building from source / development
- Limitations
- License
Homepage's services.yaml is hand-edited YAML: a list of sections, each containing a list
of services with fields like icon, href, description, ping, and widgets. Homepage GUI
gives that file a fast, modern editor:
- Mount your existing Homepage
configdirectory into the container. - Edit
services.yamlvisually in your browser, on any LAN device. - Saves write straight back to the same file Homepage reads, with a timestamped backup every time. Homepage hot-reloads, so changes appear immediately (only new icon uploads need a Homepage restart, which the app can do for you).
- Drag & drop — reorder sections, reorder services within a section, and drag services between sections.
- Sidebar — section navigator with a live filter, plus draggable "Service" / "Section" blocks you can drop onto the canvas or click to append.
- Full service editing — name, icon, URL (
href), description andpingas first-class fields, plus an Advanced (YAML) panel that round-trips widgets,server/containerand any other keys without losing them. - Icon chooser with combined search across all sources (origin-badged), or per-source tabs:
- Dashboard Icons (
name.svg) · Material Design Icons (mdi-) · Font Awesome (fas-/far-/fab-) · SVG / freesvgicons (200k+ Iconify icons, stored as a URL). - My Uploads — upload your own PNG/SVG files.
- Dashboard Icons (
- Icon color overrides — for sources Homepage can recolor (
mdi-/si-/sh-via-#hex, and Iconify SVG URLs via?color=), with a color picker. - Alphabetical sort per section (A→Z / Z→A).
- Safe saves — generated YAML is validated before writing, writes are atomic, and a
timestamped backup is taken first. Common fields are emitted in Homepage's conventional
order (
icon,href,description,ping); empty fields stay blank, notnull. - Backups & restore in the UI, with 14-day auto-purge (configurable) and a count cap.
- YAML preview before saving.
- One-click Homepage restart so newly-uploaded icons get served.
- Sign-in, set up on first boot — a short wizard creates your admin account; after that the whole app (UI and API) is behind a login, with optional Cloudflare Turnstile. See The login.
- In-app release notes (click the version in the sidebar footer).
- Self-hosted Inter font and cache-busted assets; in-app Source link (AGPL §13).
- A host running Docker and Docker Compose v2 (
docker compose …). - An existing Homepage install whose
configdirectory (containingservices.yaml) is on the same host. - A browser with internet access for icon search/preview (Iconify & jsDelivr CDNs — the same ones Homepage uses). Editing and saving work fully offline.
Docker Compose is the supported way to run Homepage GUI.
git clone https://github.com/hyprlab/homepage-gui.git
cd homepage-guiThe repo ships a ready-to-use compose.yaml that pulls hyprlab/homepage-gui:latest:
services:
homepage-gui:
image: ${IMAGE:-hyprlab/homepage-gui:latest}
build: .
container_name: homepage-gui
restart: unless-stopped
user: "0:0" # needed to write a root-owned services.yaml
environment:
- HOMEPAGE_CONFIG_DIR=/config
- BACKUP_DIR=/config/.homepage-gui-backups
- KEEP_BACKUPS=40
- KEEP_BACKUP_DAYS=14
- ICONS_DIR=/icons
- HOMEPAGE_CONTAINER=${HOMEPAGE_CONTAINER:-homepage}
ports:
- "${HOST_PORT:-5005}:5000"
volumes:
- ${HOST_CONFIG_DIR:-./config}:/config
- ${HOST_ICONS_DIR:-./icons}:/icons
- /var/run/docker.sock:/var/run/docker.sockCopy the example and point it at your host paths:
cp .env.example .env# .env
HOST_CONFIG_DIR=/path/to/homepage/config # folder containing services.yaml
HOST_ICONS_DIR=/path/to/homepage/icons # shared custom-icons folder (see step 3)
HOST_PORT=5005 # browse to http://<host>:5005
HOMEPAGE_CONTAINER=homepage # your Homepage container's nameTip: verify the resolved config before starting with
docker compose config.
Homepage serves local icons from /app/public/icons (referenced as /icons/<file>).
For uploads to appear in Homepage, the same host folder must be mounted into both
containers. Add this volume to your Homepage compose.yaml:
services:
homepage:
volumes:
- /path/to/homepage/config:/app/config
- /path/to/homepage/icons:/app/public/icons # <-- add this (matches HOST_ICONS_DIR)
- /var/run/docker.sock:/var/run/docker.sockThen recreate Homepage once: docker compose up -d in your Homepage directory.
The Docker socket mount on the GUI lets its Restart Homepage button apply new icons. If you'd rather not expose the socket, omit that volume — uploads still work, you'll just restart Homepage yourself.
docker compose up -dOpen http://<host>:5005 (the port from HOST_PORT) on any device on your LAN.
The first visit runs a short setup wizard that creates your admin account — see The login. After that you'll be signed in and looking at your dashboard.
These are set in compose.yaml's environment: (container-side) and .env (host-side).
Container environment variables
| Variable | Default | Purpose |
|---|---|---|
HOMEPAGE_CONFIG_DIR |
/config |
Directory (inside the container) holding services.yaml |
SERVICES_PATH |
$HOMEPAGE_CONFIG_DIR/services.yaml |
Override the exact file path |
BACKUP_DIR |
$HOMEPAGE_CONFIG_DIR/.homepage-gui-backups |
Where backups are written |
KEEP_BACKUPS |
40 |
Hard cap on number of backups (0 = unlimited) |
KEEP_BACKUP_DAYS |
14 |
Auto-purge backups older than N days (0 = keep forever) |
ICONS_DIR |
/icons |
Where uploaded icons are stored (shared with Homepage) |
HOMEPAGE_CONTAINER |
homepage |
Container the GUI restarts to load new icons |
DOCKER_SOCK |
/var/run/docker.sock |
Docker socket used for the restart |
SOURCE_URL |
this repo | Source link shown in-app (set to your fork if modified) |
PORT |
5000 |
In-container listen port (host port is mapped in compose) |
DATA_DIR |
$HOMEPAGE_CONFIG_DIR/.homepage-gui |
Holds the account database and session key |
SECRET_KEY |
auto | Session signing key; generated and persisted in DATA_DIR if unset |
TURNSTILE_SITE_KEY |
(empty) | Cloudflare Turnstile site key — empty disables the challenge |
TURNSTILE_SECRET_KEY |
(empty) | Turnstile secret key (both must be set to enable it) |
DATABASE_URL |
sqlite:///$DATA_DIR/homepage-gui.db |
Override the account database location |
.env (host-side, used by compose)
| Variable | Example | Purpose |
|---|---|---|
HOST_CONFIG_DIR |
/srv/homepage/config |
Host path mounted to /config |
HOST_ICONS_DIR |
/srv/homepage/icons |
Host path mounted to /icons |
HOST_PORT |
5005 |
Host port mapped to the container's 5000 |
HOMEPAGE_CONTAINER |
homepage |
Passed through for the restart feature |
IMAGE |
hyprlab/homepage-gui:1.0.0 |
Pin a specific image tag (optional) |
TURNSTILE_SITE_KEY / TURNSTILE_SECRET_KEY |
0x4AAA… |
Cloudflare Turnstile on the login (optional) |
SECRET_KEY |
a1b2c3… |
Pin the session signing key (optional) |
The container runs as root (user: "0:0") so it can write a typically root-owned
services.yaml. Change user: if your config files are owned by a different UID/GID.
Homepage GUI edits the file your dashboard runs on, so it ships with a sign-in. The first time you open it, a short wizard creates your admin account:
- Welcome — what's about to happen.
- Create the admin account — name (optional), username, password (8+ characters).
- You're all set — confirms which
services.yamlit will write to, and whether that file is actually writable, so a bad mount shows up here rather than on your first save.
You're signed in when the wizard finishes. There's one account and no registration page — this is a single-operator tool. Keep the password in your password manager; there's no reset link, and see Forgot the password if you lose it.
Everything else is guarded: every page and every /api/* route needs a session. Sign out
from the button in the top bar.
Where the account lives
The account database and the session signing key sit in DATA_DIR, which defaults to
.homepage-gui/ inside the config directory you already mount — so they survive
docker compose up -d and container recreates with no extra volume. Point DATA_DIR
somewhere else (a named volume, say) if you'd rather keep them out of your Homepage config:
environment:
- DATA_DIR=/data
volumes:
- homepage-gui-data:/dataIf the GUI is reachable from the internet, you can put a Turnstile challenge on the login.
Create a widget at Cloudflare dashboard → Turnstile, then put the pair in .env:
TURNSTILE_SITE_KEY=0x4AAAAAAA...
TURNSTILE_SECRET_KEY=0x4AAAAAAA...The challenge renders on the sign-in page and is verified server-side against Cloudflare before the password is ever checked. Leave either value empty and the challenge is skipped entirely — no Cloudflare account needed for LAN use. If Cloudflare can't be reached, the login fails closed rather than waving people through.
There's no reset link, but the account row is yours to delete — remove the database and the next start runs the setup wizard again:
docker compose down
sudo rm /path/to/homepage/config/.homepage-gui/homepage-gui.db
docker compose up -dYour services.yaml, backups and uploaded icons are untouched by this.
- The session cookie is
HttpOnlyandSameSite=Lax, signed withSECRET_KEY(generated and persisted inDATA_DIRwhen unset, so sign-ins survive restarts). - Keep me signed in issues a long-lived remember cookie; leave it unticked and the session ends with the browser.
- Writes (
POST/PUT/PATCH/DELETE) carry a per-session CSRF token — as a hidden_csrffield in forms, or anX-CSRFheader from the editor's own API calls. If you script against the API, read the token from the<meta name="csrf">tag on any page and send it in that header, reusing the same cookie jar. /api/healthstays reachable without a session for container health checks, but reports nothing beyond{"ok": true}until you sign in.- If you expose the GUI beyond your LAN, put it behind HTTPS — over plain HTTP the session cookie travels in the clear.
- Add a section — the
+in the sidebar, or drag the "Section" block onto the canvas. - Add a service — a section's
+, or drag the "Service" block into a section. - Edit — click ✎ on a card (or double-click it) to open the editor; set fields and pick an icon. Use Advanced (YAML) for widgets and other keys.
- Reorder / move — drag the ⠿ handles; drag services across sections.
- Sort a section — the ⇅ button (toggles A→Z / Z→A).
- Preview — see the exact YAML before saving.
- Save —
Ctrl/Cmd+Sor the Save button. A backup is taken automatically. - Backups — open the Backups dialog to restore a previous version.
In the icon chooser, the My Uploads tab lets you upload PNG/SVG icons (button or
drag-and-drop). They're saved to the shared icons folder and referenced as /icons/<file>.
Because Homepage only reads public/icons at startup, new uploads require a Homepage
restart — use the Restart Homepage button in the sidebar (it restarts the
HOMEPAGE_CONTAINER via the Docker socket). Workflow: upload → select the icon for a
service → Save → Restart Homepage.
Every save and restore first writes a timestamped copy to BACKUP_DIR
(/config/.homepage-gui-backups/ by default — a dot-folder Homepage ignores). Backups older
than KEEP_BACKUP_DAYS (default 14) are purged automatically, with KEEP_BACKUPS
(default 40) as a hard cap. Purging runs on each save and whenever the page or Backups dialog
loads. Restore or inspect any backup from the Backups dialog.
cd homepage-gui
docker compose pull # fetch the latest image
docker compose up -d # recreate the containerTo pin a version, set IMAGE=hyprlab/homepage-gui:1.0.0 in .env.
Icon previews and search use public CDNs (the Iconify API and jsDelivr), so the browser you edit from needs internet access — the same CDNs Homepage itself uses for dashboard icons. Core editing and saving work fully offline; only icon search/preview needs the network.
See CHANGELOG.md, or click the version number in the app's sidebar footer for in-app release notes (both read from the same source).
Build and run the image locally instead of pulling:
docker compose up -d --buildRun the Flask app directly (without Docker) for development:
pip install -r requirements.txt
HOMEPAGE_CONFIG_DIR=/path/to/homepage/config \
ICONS_DIR=/path/to/homepage/icons \
DATA_DIR=./devdata \
python app.py # serves on http://localhost:5000The first run drops you in the setup wizard. DATA_DIR keeps the dev account database out
of your real config directory; delete that folder to start over. Turnstile stays off unless
you set the two TURNSTILE_* variables.
Stack: Flask + Flask-Login + SQLAlchemy + PyYAML + gunicorn (backend); vanilla JS + SortableJS + js-yaml (frontend); bundled Inter (SIL OFL). No build step; the only database is a single-table SQLite file holding the admin account.
- Comments in
services.yaml(other than the standard header) are not preserved — the file is regenerated from the parsed structure. The previous version is always backed up first. - Group-level settings (a section whose value is a mapping rather than a list of services) are shown read-only and preserved verbatim; edit those via Preview/raw if needed.
Homepage GUI is built by a human maintainer working with generative AI as a development tool:
- Code — the large majority of the Python and JavaScript in this repository was written with Anthropic's Claude (via Claude Code), working from the maintainer's direction. The maintainer decides what gets built, reviews the results, tests every release, and signs off on everything that ships.
- Text — documentation, release notes, and in-app copy are largely AI-drafted and human-edited.
- The app itself contains no AI. Homepage GUI has no AI features and makes no requests to AI services — it only reads and writes the services.yaml on your own server. AI was used to build the app, not to run it.
Bug reports and pull requests are welcome from humans and their AI tools alike; everything merged gets the same human review.
Licensed under the GNU Affero General Public License v3.0 — see LICENSE.
Because Homepage GUI is network-served software, AGPL §13 requires that users who interact
with a modified version over a network can obtain its corresponding source. The in-app
Source link and SOURCE_URL exist for this — point them at your fork if you modify it.