Deploy Heimdall with Docker Compose — a simple application dashboard for your homelab links.
Kubernetes version: heimdall-k8s
Uses the official php:8.4-apache image and builds Heimdall from the upstream release (no LinuxServer container runtime).
Updating an older clone?
git pullalone will not wipe data. Re-running install/compose can break a LinuxServer-based install. Read BREAKING-CHANGES.md first.
One-command Heimdall dashboard — official PHP build of upstream Heimdall, interactive install, backups.
Choose your path: Docker Compose (this repo) · Kubernetes
Good fit: a simple start-page for your self-hosted apps on Docker.
Not for: reusing old LinuxServer /config volumes — see BREAKING-CHANGES if migrating.
./manage.shcontrol center — install, update, backup, status/doctor, uninstall- Interactive colored install with step progress
- Auto-detects your OS and installs missing host tools
- Safe
./manage.sh updatewith automatic pre-update backup - Incremental hardlink
./manage.sh backup+ restore - Official upstream images only
If this stack saved you setup time, please consider sponsoring — it funds:
- Keeping install/update/backup scripts working across common Linux distros
- Testing safe upgrades against official upstream images
- Building more beginner-friendly stacks that share the same
./manage.shUX
👉 github.com/sponsors/johnycsf
- A Linux host (Debian/Ubuntu, Fedora/RHEL, Arch, openSUSE, Alpine) or macOS with Homebrew
sudoso./manage.shcan install missing tools (Docker, curl, openssl, rsync, …)- Enough disk for your data
./manage.sh is interactive (colors + step progress), detects your OS, and installs host dependencies automatically.
git clone https://github.com/johnycsf/heimdall-docker.git
cd heimdall-docker
chmod +x manage.sh
./manage.sh # interactive control center
# or: ./manage.shOr:
cp .env.example .env
docker compose up -d --buildOpen http://YOUR_IP:8080/ (or the HTTP_PORT from .env).
Liked the install? Star the repo or sponsor johnycsf so more stacks stay maintained.
Edit .env:
| Variable | Default | Purpose |
|---|---|---|
TZ |
America/New_York |
Timezone |
HTTP_PORT |
8080 |
Host port (Docker users may set 80) |
ALLOW_INTERNAL_REQUESTS |
true |
Allow Heimdall to reach LAN app IPs |
APP_URL |
http://localhost:8080 |
Public URL (set this if you use a reverse proxy) |
Keep the stack current (safe while running; brief recreate downtime):
./manage.sh updateBefore changing anything, the script runs ./manage.sh backup into ./backups (incremental, database-safe). After a successful update it asks whether to keep or delete that snapshot, and how many local copies to retain (older ones are pruned). Copy important backups to an external drive, NAS, or cloud so they do not fill this disk.
To roll back later (same tool as disaster recovery):
./manage.sh backup --restore --from ./backups
# or from an external copy:
./manage.sh backup --restore --from /mnt/usb/my-backupsOlder backups/update-* tarball folders (from previous script versions) are no longer used by ./manage.sh update; use each folder's RESTORE.txt if you still need one, or delete them to free space.
This pulls/rebuilds images, recreates containers as needed, and runs docker image prune for dangling (untagged) images only — it will not wipe other projects' images or your data/ volume.
Only for installs already on this repo's official-php image — see BREAKING-CHANGES.md.
Incremental snapshots via rsync hardlinks (unchanged files are not re-copied). ./manage.sh update uses this same backup.sh before updating (into ./backups).
# Backup to USB/NAS/external path (repeat anytime; later runs are incremental)
./manage.sh backup --dest /mnt/usb/heimdall-docker-backups
./manage.sh backup --dest /mnt/usb/heimdall-docker-backups --keep 5 # optional: retain only newest N
# On a brand-new machine/cluster after ./manage.sh:
./manage.sh backup --restore --from /mnt/usb/heimdall-docker-backups
# or a specific snapshot:
./manage.sh backup --restore --from /mnt/usb/heimdall-docker-backups/snapshots/YYYYMMDD-HHMMSSEach snapshot includes SHA256SUMS plus a snapshot_sha256 key in META.txt. Restore verifies these and warns (does not abort) if integrity is lost.
Keep the backup root on one filesystem so hardlinks work. Prefer an external drive, NAS, or cloud sync of that folder.
Database safety: Nextcloud uses a verified MariaDB logical dump (mariadb-dump --single-transaction) — the live data/db / DB PVC files are never rsync'd. SQLite apps (Heimdall, Vaultwarden) are stopped or scaled to 0, WAL-checkpointed when sqlite3 is available, integrity-checked, then copied. Incremental hardlinks apply to file trees; each SQL dump is a full verified file with a SHA-256 in META.txt.
docker compose down
# optional: delete local data
rm -rf dataThis repo packages or configures upstream software. See CREDITS.md for the main developers and projects this work builds on.
This project is provided as is. The author is not responsible for any loss, damage, data corruption, downtime, security issues, or other consequences from using it. Full text: DISCLAIMER.md.
If you hit an error, please open a GitHub Issue and follow CONTRIBUTING.md. Fixes via Pull Request are welcome. GitHub Issues/PRs are the supported way to report problems—there is no private support channel.
./manage.sh opens a simple ↑/↓ menu with a > cursor (j/k and Enter also work). No extra packages required.
During ./manage.sh (or Manage → Install / reconfigure), the script checks whether default host ports are free, lets you keep the defaults or choose different ports, and saves them in .env. Re-running install keeps your current ports unless you change them.
Non-interactive: set the port variables in .env (or the environment) and use SKIP_PORT_PROMPTS=1.
Defaults are kept unique across the johnycsf stacks so you can run several on one host without a clash:
| Stack | Variable | Default host port |
|---|---|---|
heimdall-docker |
HTTP_PORT |
8080 |
vaultwarden-docker |
PORT |
8081 |
nextcloud-office-docker |
NEXTCLOUD_PORT |
8082 |
nextcloud-office-docker |
COLLABORA_PORT |
9980 |
immich-docker |
IMMICH_PORT |
2283 |
Install also refuses a port another stack checked out beside this one already claims in its .env — even when that stack is stopped — and offers the next free port instead.
All defaults are >= 1024 because rootless Podman cannot publish privileged ports (80, 443). On Docker you may still set HTTP_PORT=80 if you want.
During ./manage.sh → Install you can choose Docker or Podman. The choice is saved as CONTAINER_ENGINE in .env. All manage actions (update, backup, restore, …) use that engine via a shared compose helper.
Note: After containers start, some files under
data/may be root-owned. Install/restore automatically fixes ownership for the invoking user so host-sidersyncbackup/restore does not fail with permission errors.
Local snapshots stay as incremental hardlink trees (fast rollback). Optionally create a compressed offsite copy with ./manage.sh backup --dest ./backups --archive tar.gz|tar.xz|zip (add --archive-password for zip password or age-passphrase on tar). For stronger key-based encryption use --encrypt (age). See repo-framework docs/BACKUP_ENCRYPTION.md.
See SECURITY.md for how to report vulnerabilities.