How to Install Homepage Dashboard on Ubuntu 24.04 VPS
Self-hosted servers tend to sprawl. Within a few months you have Jellyfin on one port, Sonarr and Radarr on another, Portainer behind a reverse proxy, Prometheus scraping half a dozen exporters, and a handful of raw links pinned in a browser tab that nobody else can find. Homepage is the clean, fast, modern dashboard that pulls everything back into a single landing page -- with live widgets, bookmarks, and automatic Docker discovery -- all driven by a handful of YAML files.
This guide walks you through installing Homepage on an Ubuntu 24.04 VPS with Docker Compose, wiring up service widgets for Sonarr, Radarr, Plex, Jellyfin, and Portainer, enabling Docker auto-discovery via labels, and putting it behind an Nginx reverse proxy with TLS.
Skip the setup? Launch a ready-to-use Docker host in under 60 seconds with our CloudCore Starter VPS -- 4 vCPU, 8 GB RAM, 100 GB NVMe, more than enough for Homepage plus every service it surfaces.
Table of Contents
What is Homepage?
Homepage (project repo: github.com/gethomepage/homepage) is a modern, open-source application dashboard built on Next.js, React, and Tailwind CSS. It replaces older PHP-based dashboards like Heimdall and lighter static grids like Organizr or Dashy with something notably faster -- the entire dashboard is server-rendered, static-cached, and ships in a Docker image under 200 MB.
The dashboard is configured entirely through human-readable YAML files. There is no admin UI and no database. You edit services.yaml, bookmarks.yaml, widgets.yaml, and settings.yaml, Homepage hot-reloads the changes, and the dashboard updates. That design choice keeps configuration diffable in Git, reproducible across servers, and easy to migrate.
Where Homepage really earns its place is the service widget system. More than 100 built-in integrations pull live data directly from the API of the services you run. A Sonarr widget shows how many episodes aired this week. A Radarr widget counts movies missing from your library. A Plex or Jellyfin widget shows active transcodes. A Prometheus widget surfaces a custom query value. A Proxmox widget shows VM counts and cluster health. A Portainer widget shows running containers. A Speedtest Tracker widget shows latest download speed. You wire these up with three or four lines of YAML per service.
On top of that, Homepage can read the Docker socket (or a socket proxy) and automatically discover running containers using labels -- so a newly started Jellyfin container appears on the dashboard with zero extra configuration beyond a few labels: entries in its own compose file.
Why Run Homepage on Your VPS?
For anyone running a homelab or a self-hosted stack on a VPS, Homepage solves three concrete problems:
- One place to land -- Instead of remembering
:9091,:8096,:9000,:3000, and a dozen other ports, you visitdashboard.yourdomain.comand click through from there. Every service is one tile away. - Live status at a glance -- Widgets surface the metrics you actually care about (unread messages, failed backups, running downloads, temperature, CPU load) without needing to open each service.
- Shared onboarding -- New teammates, family members, or clients get one URL and can reach everything without a cheat sheet.
Typical VPS Requirements
Homepage itself is extremely lightweight. The resource footprint comes from everything else you run alongside it.
| Setup | CPU | RAM | Disk | Notes |
|---|---|---|---|---|
| Homepage only | 1 vCPU | 256 MB | 1 GB | Demo / companion to existing stack |
| Homepage + 3-5 services (Portainer, Nginx Proxy Manager, Jellyfin) | 2 vCPU | 4 GB | 40 GB | Light homelab |
| Homepage + full *arr stack + media server | 4 vCPU | 8 GB | 100+ GB | CloudCore Starter territory |
| Homepage + everything above + databases + monitoring | 6 vCPU | 16 GB | 200+ GB | Large homelab |
Recommended Plan: CloudCore Starter>
For a typical self-hosted stack behind Homepage (Portainer + Jellyfin + Sonarr/Radarr + Nginx Proxy Manager + Uptime Kuma), we recommend the CloudCore Starter plan:>
- 4 vCPU cores
- 8 GB RAM
- 100 GB NVMe SSD
- Unmetered bandwidth>
Plenty of headroom for Homepage plus the services it is pointing at.
Prerequisites
Before starting, make sure you have:
- A VPS running Ubuntu 24.04 LTS with root or sudo access
- SSH access to the server
- A domain name (optional but recommended) with an A record pointing at your VPS IP if you plan to expose Homepage publicly
- Docker and Docker Compose installed -- if you do not have them yet, follow our guides:
Connect to your server:
ssh root@your-server-ipStep 1: Update System Packages
Start with a clean, patched system:
sudo apt update && sudo apt upgrade -yIf the kernel was updated, reboot:
sudo rebootReconnect via SSH once the server is back up.
Step 2: Install Docker and Docker Compose
If you already have Docker installed, skip to Step 3. Otherwise, the short version:
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
newgrp dockerVerify both:
docker --version
docker compose versionExpected output:
Docker version 27.3.1, build ce12230
Docker Compose version v2.29.7For the full walkthrough (repository setup, post-install hardening, log rotation, rootless mode), see How to Install Docker on Ubuntu 24.04 and How to Install Docker Compose on Ubuntu 24.04.
Step 3: Create the Homepage Project Directory
Keep all Homepage files in one place so upgrades and backups are trivial.
sudo mkdir -p /opt/homepage/config
sudo mkdir -p /opt/homepage/icons
cd /opt/homepageThe config/ directory holds the YAML files and is mounted into the container. The icons/ directory lets you drop in custom logos -- useful for self-hosted apps that do not have an entry in the built-in Dashboard Icons set.
Step 4: Write the docker-compose.yml
Create the compose file:
sudo nano /opt/homepage/docker-compose.ymlPaste in:
services:
homepage:
image: ghcr.io/gethomepage/homepage:latest
container_name: homepage
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- ./config:/app/config
- ./icons:/app/public/icons
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
- HOMEPAGE_ALLOWED_HOSTS=dashboard.yourdomain.com,localhost:3000
- PUID=1000
- PGID=1000
- TZ=Europe/BerlinA few things to call out:
HOMEPAGE_ALLOWED_HOSTS-- Required in Homepage v0.9.0+. The app refuses requests whoseHostheader is not in this list. Add every hostname you will use (your domain,localhost:3000for local testing, the VPS IP if you hit it directly)./var/run/docker.sock:/var/run/docker.sock:ro-- Read-only mount of the Docker socket. This enables auto-discovery and the Docker widget. See the security note below../config:/app/config-- On first boot, Homepage populates this directory with example YAML files you can edit.
sudo docker compose up -dCheck the logs to confirm a clean boot:
sudo docker compose logs -f homepageExpected output:
homepage | ▲ Next.js 14.2.x
homepage | - Local: http://localhost:3000
homepage | - Network: http://0.0.0.0:3000
homepage | ✓ Starting...
homepage | ✓ Ready in 1.2sVisit http://your-server-ip:3000 and you will see the default Homepage with placeholder tiles.
Security Note: Docker Socket
Mounting /var/run/docker.sock directly gives the container root-equivalent access to the host. The read-only flag (:ro) reduces but does not eliminate the risk. For production, use a socket proxy instead:
services: dockerproxy: image: ghcr.io/tecnativa/docker-socket-proxy:latest container_name: dockerproxy environment: - CONTAINERS=1 - SERVICES=1 - TASKS=1 - POST=0 volumes: - /var/run/docker.sock:/var/run/docker.sock:ro restart: unless-stopped ports: - "127.0.0.1:2375:2375"
homepage: # ... volumes: - ./config:/app/config - ./icons:/app/public/icons # Remove the docker.sock volume mount # Then in services.yaml, point Docker integrations at: tcp://dockerproxy:2375
The proxy exposes only read-only container and service endpoints, not the full Docker API.
Step 5: Configure settings.yaml
On first boot Homepage created starter files inside /opt/homepage/config/. Open settings.yaml:
sudo nano /opt/homepage/config/settings.yamlReplace its contents with:
title: My Homelab favicon: https://gethomepage.dev/img/icon.pngtheme: dark color: slate
layout: Media: style: row columns: 4 Downloads: style: row columns: 3 Infrastructure: style: row columns: 4
background: image: https://images.unsplash.com/photo-1451187580459-43490279c0fa blur: sm saturate: 50 brightness: 50 opacity: 50
headerStyle: clean language: en target: _blank quicklaunch: searchDescriptions: true hideInternetSearch: false showSearchSuggestions: true hideVisitURL: false
What each block does:
theme/color--themeacceptsdarkorlight.colorsets the accent palette (slate,gray,zinc,red,orange,amber,yellow,lime,green,emerald,teal,cyan,sky,blue,indigo,violet,purple,fuchsia,pink,rose).layout-- Controls how each group (defined inservices.yaml) renders.style: rowlays tiles out horizontally with the specified number ofcolumns;style: columnstacks them vertically. Groups not listed here fall back to defaults.background-- Any HTTPS image URL or a path under/app/public/icons/mapped to your./iconsvolume.blur,saturate,brightness, andopacitytune how strongly it shows through.headerStyle--clean,boxed,underlined, orboxedWidgets.language-- Homepage is translated into 30+ languages (en,de,fr,es,it,nl,pl,pt,ru,zh-CN,ja, ...).target: _blank-- Opens every service link in a new tab. Change to_selffor same-tab navigation.
Step 6: Add Services and Widgets
services.yaml is where Homepage really shines. Open it:
sudo nano /opt/homepage/config/services.yamlHere is a realistic example covering Sonarr, Radarr, Plex, Jellyfin, and Portainer:
- Media: - Jellyfin: icon: jellyfin.png href: https://jellyfin.yourdomain.com description: Media server widget: type: jellyfin url: http://jellyfin:8096 key: your_jellyfin_api_key_here enableBlocks: true enableNowPlaying: true- Plex: icon: plex.png href: https://plex.yourdomain.com description: Media server widget: type: plex url: http://plex:32400 key: your_plex_token_here
- Sonarr: icon: sonarr.png href: https://sonarr.yourdomain.com description: TV show management widget: type: sonarr url: http://sonarr:8989 key: your_sonarr_api_key_here
- Downloads:
- Radarr: icon: radarr.png href: https://radarr.yourdomain.com description: Movie management widget: type: radarr url: http://radarr:7878 key: your_radarr_api_key_here
- qBittorrent: icon: qbittorrent.png href: https://qbit.yourdomain.com description: Torrent client widget: type: qbittorrent url: http://qbittorrent:8080 username: admin password: your_qbit_password
- Portainer: icon: portainer.png href: https://portainer.yourdomain.com description: Docker management widget: type: portainer url: https://portainer:9443 env: 2 key: your_portainer_access_token
- Infrastructure:
- Nginx Proxy Manager: icon: nginx-proxy-manager.png href: https://npm.yourdomain.com description: Reverse proxy widget: type: npm url: http://nginxproxymanager:81 username: [email protected] password: your_npm_password
- Uptime Kuma: icon: uptime-kuma.png href: https://status.yourdomain.com description: Monitoring widget: type: uptimekuma url: http://uptime-kuma:3001 slug: default
Finding API Keys
- Sonarr / Radarr / Prowlarr / Lidarr -- Settings -> General -> Security -> API Key
- Jellyfin -- Dashboard -> API Keys -> New API Key
- Plex -- Sign in, open any item in the web UI, view XML. The token appears in the URL as
X-Plex-Token=.... Or see the Plex authentication guide. - Portainer -- Account icon -> My Account -> Access tokens -> Add access token
- Prometheus -- No key required unless you put basic auth in front
Step 7: Add Bookmarks
Bookmarks are for sites you do not want a live widget for -- documentation, external tools, admin panels on other providers. Open:
sudo nano /opt/homepage/config/bookmarks.yamlExample:
- Developer:
- Github:
- abbr: GH
href: https://github.com/
- Stack Overflow:
- abbr: SO
href: https://stackoverflow.com/- Cloud:
- Cloudflare:
- abbr: CF
href: https://dash.cloudflare.com/
- vps-server.host:
- abbr: VH
href: https://vps-server.host/- Reference:
- Docker Hub:
- abbr: DH
href: https://hub.docker.com/
- Homepage Docs:
- abbr: HP
href: https://gethomepage.dev/Each bookmark needs either an abbr (shown in the tile) or an icon. Groups ( Developer, Cloud, Reference) render as columns on the bookmarks row.
Step 8: Add Information Widgets
Information widgets sit at the top of the dashboard and show system metrics, weather, search, and more. Open:
sudo nano /opt/homepage/config/widgets.yamlExample:
- resources:
cpu: true
memory: true
disk: /- search:
provider: duckduckgo
target: _blank- datetime:
text_size: xl
format:
timeStyle: short
dateStyle: long- openmeteo:
label: Berlin
latitude: 52.52
longitude: 13.41
timezone: Europe/Berlin
units: metric
cache: 5- greeting:
text_size: xl
text: Welcome backUseful information widgets:
resources-- Host CPU/memory/disk usage (requires Docker socket access or aglancescontainer)search-- Top-of-page search bar (google,duckduckgo,bing,brave,qwant,kagi, or a custom URL)datetime-- Live clockopenmeteo/weatherapi-- Weatherstocks-- Stock tickerunifi_console-- UniFi controller statusglances-- Full system monitoring (CPU, memory, disk, network, processes) via a companion Glances container
Step 9: Enable Docker Auto-Discovery
This is the feature that keeps your YAML files short as your stack grows. With the Docker socket mounted (Step 4), any container labelled with homepage.* automatically appears on the dashboard.
Add labels to other services in their own compose files. Example for a Jellyfin container elsewhere on the host:
services:
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
ports:
- "8096:8096"
volumes:
- ./config:/config
- ./cache:/cache
- /mnt/media:/media
restart: unless-stopped
labels:
- homepage.group=Media
- homepage.name=Jellyfin
- homepage.icon=jellyfin.png
- homepage.href=https://jellyfin.yourdomain.com
- homepage.description=Media server
- homepage.widget.type=jellyfin
- homepage.widget.url=http://jellyfin:8096
- homepage.widget.key=your_jellyfin_api_key_hereRestart the service (docker compose up -d) and Homepage will pick it up within seconds -- no edit to services.yaml required.
For the tile to inherit sorting from an existing group, the homepage.group label must exactly match a group name already defined in services.yaml (or another discovered container). If the group does not exist, Homepage creates it automatically at the end of the dashboard.
Docker Integration Block
If you want the Docker widget itself (showing container counts per host), add this to docker.yaml:
sudo nano /opt/homepage/config/docker.yamlmy-docker:
socket: /var/run/docker.sockThen reference server: my-docker in any service in services.yaml to have Homepage display its container status on the tile.
Step 10: Reverse Proxy with Nginx and TLS
Exposing port 3000 directly works, but you want a real hostname and HTTPS.
Install Nginx and Certbot:
sudo apt install -y nginx certbot python3-certbot-nginxCreate the site config:
sudo tee /etc/nginx/sites-available/homepage > /dev/null <<'EOF' server { listen 80; server_name dashboard.yourdomain.com;location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket support (for live widgets that stream) proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";
proxy_read_timeout 300s; } } EOF
Enable the site and fetch a certificate:
sudo ln -s /etc/nginx/sites-available/homepage /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d dashboard.yourdomain.comCertbot rewrites the config to listen on 443 with your Let's Encrypt certificate and auto-renews.
Make sure your hostname is included in HOMEPAGE_ALLOWED_HOSTS in docker-compose.yml. If you added it after first boot, restart the container:
cd /opt/homepage
sudo docker compose up -dVisit https://dashboard.yourdomain.com and you should see your dashboard over TLS.
Upgrading Homepage
Homepage ships frequent releases. The upgrade flow is two commands:
cd /opt/homepage
sudo docker compose pull
sudo docker compose up -dpull fetches the newest image tagged latest, up -d recreates the container with the new image, and your ./config volume carries over unchanged. If a release introduces breaking config changes, the Homepage release notes flag them and give a migration snippet -- always skim the changelog before upgrading across minor versions.
To pin a specific version (recommended for production), change the image tag in docker-compose.yml:
image: ghcr.io/gethomepage/homepage:v0.10.9Then bump it deliberately when you want to upgrade.
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
Error: Invalid Host header in the browser | Hostname not in HOMEPAGE_ALLOWED_HOSTS | Add it to the env var in docker-compose.yml, then docker compose up -d |
Widget shows Error or -- -- instead of data | Wrong URL, wrong API key, or service unreachable from inside the container | Check logs: docker compose logs homepage. Verify the URL works from inside: docker exec -it homepage wget -qO- http://sonarr:8989. Confirm the API key is fresh. |
| Docker auto-discovery not picking up containers | Socket not mounted, or containers on a different Docker network | Confirm /var/run/docker.sock is mounted :ro in compose. Labels must be applied on the container itself, not the compose service. Restart the labelled container. |
permission denied on docker.sock | Container user cannot read the socket | On most hosts the socket is owned by root:docker. Homepage runs as root inside the container by default -- if you set PUID/PGID, ensure that UID is in the docker group on the host, or use the socket proxy approach. |
| Blank page / white screen after upgrade | Browser cached an old build | Hard reload (Ctrl+Shift+R), or clear site cache. If it persists, docker compose down && docker compose up -d |
| Services.yaml edits not appearing | YAML syntax error | Check logs -- Homepage prints parse errors. Validate with yamllint config/services.yaml |
| Weather widget shows nothing | Missing latitude/longitude or wrong timezone | openmeteo requires both latitude and longitude -- there is no geocoding step |
Error response from daemon: connection refused in Docker widget | Socket proxy not reachable or Docker daemon not running | systemctl status docker. For socket proxy, check docker logs dockerproxy |
cd /opt/homepage
sudo docker compose logs -f homepageFAQ
Is Homepage free?
Yes. Homepage is MIT-licensed, open source, and free for any use -- personal or commercial. The project is maintained on GitHub at gethomepage/homepage with an active community of contributors.
Does Homepage require Node.js on the host?
No. The Docker image ships a prebuilt Next.js standalone server. The host only needs Docker. If you prefer running without Docker, Homepage also publishes a Node package (pnpm install && pnpm build && pnpm start), but Docker is by far the most common deployment.
Can Homepage authenticate users?
Homepage has no built-in authentication. The dashboard is either public or not, depending on how you expose it. For private use, put it behind an authenticating reverse proxy like Authelia, Authentik, or Nginx with basic auth. For internal-only use, bind the port to 127.0.0.1 and reach it via a VPN like Tailscale or WireGuard.
How does Homepage compare to Heimdall, Dashy, and Organizr?
Heimdall is the classic PHP dashboard -- still works, but no live widgets of the depth Homepage offers and slower on modern hardware. Dashy is feature-rich (status checks, themes, sections, PWA support) and a solid alternative, but heavier and slower to render. Organizr targets the media-server crowd with tabbed iframes and user management, but the codebase is older and widgets are limited. Homepage hits a sweet spot: fast Next.js rendering, the largest widget catalog, YAML-first config, and Docker auto-discovery. For a brand-new dashboard in 2026, Homepage is the pick for most users.
Can Homepage show widgets for services behind authentication?
Yes. Every widget accepts either an API key/token or a username/password in YAML. The credentials are read on the server side -- they are not exposed to the browser. Treat services.yaml like a secret: do not commit it publicly. Keep it in a private Git repo or use environment variable substitution via the Homepage field encryption pattern.
Does Homepage work with Kubernetes?
Yes -- there is a first-party Helm chart and Kubernetes service discovery via annotations. See gethomepage.dev/installation/k8s/. For a single-VPS homelab, Docker Compose is simpler.
Next Steps
Now that Homepage is running, the natural next steps are to surface more services on it:
- Put Portainer on the tile -- How to Install Portainer on Ubuntu 24.04 gives you a browser-based Docker GUI that pairs perfectly with Homepage's Portainer widget.
- Add a monitoring stack -- How to Build a Monitoring Stack on Ubuntu 24.04 (Prometheus + Grafana + Node Exporter). Homepage has widgets for all three.
- Run Uptime Kuma -- Install Uptime Kuma for endpoint monitoring and surface its status on Homepage via the built-in widget.
- Install Dockge or Portainer CE -- Manage your Docker stacks visually; Homepage's tiles link straight into them.
- Protect with a proper reverse proxy -- If you are not using Nginx, try Caddy for automatic TLS with zero config, or Nginx Proxy Manager for a GUI.
- Browse the widget catalog -- The full list is at gethomepage.dev/widgets/. You will almost certainly find integrations for services you already run.
Skip the Setup -- Launch a Ready-to-Use Docker Host>
Our CloudCore Starter VPS ships with Docker-ready Ubuntu 24.04, unmetered bandwidth, NVMe storage, and enough headroom for Homepage plus every service it surfaces.>
- 4 vCPU cores
- 8 GB RAM
- 100 GB NVMe SSD
- Unmetered traffic
- Deploy in under 60 seconds>
Launch Your CloudCore Starter VPS and get Homepage running tonight.