Home Assistant in Docker gives you the full smart-home dashboard on a server you control, with your automations and history staying on your own disk. This guide runs the official container with host networking, keeps the configuration in a directory you can back up, and shows how to reach the web UI from outside your network without exposing it carelessly.
The container build is called Home Assistant Container. It runs the same core as Home Assistant OS, but it has no add-on store — add-ons are installed as separate Docker containers instead. That is a fair trade on a VPS, where you already manage containers.
Before you start
You need:
- A Linux VPS running Ubuntu 22.04/24.04, Debian 12, AlmaLinux 8–9 or Rocky 8–9, with root or sudo access.
- Docker Engine and the Docker Compose plugin installed. If they are not, install them from Docker's own repository for your distribution.
- SSH access to the server. Examples below use
203.0.113.10and the userdeploy. - Your timezone in
Area/Cityform, for exampleEurope/Berlin. - TCP port 8123 reachable from wherever you will open the dashboard.
Note: Home Assistant uses host networking, so there is no port mapping to configure. The container binds port 8123 directly on the VPS.
Create the config directory
Home Assistant writes everything — configuration.yaml, the database, custom components, TLS certificates — into /config inside the container. Mount that path to a real directory on the host so the data survives container replacement.
- Create the directory and take ownership:
sudo mkdir -p /opt/homeassistant
sudo chown deploy:deploy /opt/homeassistant- Confirm Docker is available:
docker --version
docker compose versionYou should see a Docker version line and a Compose version line. If docker compose version fails, install the Compose plugin before continuing.
Run the Home Assistant container
The official image is published on GitHub Container Registry. The stable tag tracks the current release.
docker run -d \
--name homeassistant \
--restart=always \
--privileged \
--network=host \
-e TZ=Europe/Berlin \
-v /opt/homeassistant:/config \
-v /run/dbus:/run/dbus:ro \
ghcr.io/home-assistant/home-assistant:stableWhat the flags do:
--network=hostputs the container on the host network stack, which Home Assistant needs for device discovery protocols such as mDNS and SSDP.-v /opt/homeassistant:/configpersists your configuration and database.-v /run/dbus:/run/dbus:rolets the Bluetooth integration talk to the host D-Bus.--restart=alwaysbrings the container back after a reboot or crash.-e TZ=sets the timezone used in logs and automations.
Warning: --privileged gives the container full access to host devices. It is convenient for USB Zigbee and Z-Wave radios, but if you do not use any, remove the flag and pass only the devices you need with --device=/dev/ttyUSB0.
Use Docker Compose instead
Compose is easier to update and to keep in version control. Create /opt/homeassistant/compose.yaml:
services:
homeassistant:
container_name: homeassistant
image: ghcr.io/home-assistant/home-assistant:stable
restart: unless-stopped
network_mode: host
privileged: true
environment:
TZ: Europe/Berlin
volumes:
- /opt/homeassistant:/config
- /run/dbus:/run/dbus:roThen start it:
cd /opt/homeassistant
docker compose up -dTip: With network_mode: host, any ports: block in the Compose file is ignored. Do not add one and expect it to change anything.
Finish onboarding
Open http://203.0.113.10:8123 in a browser. The first run takes a minute or two while Home Assistant builds its database.
- Create the owner account with a strong password. This account is local to your instance.
- Name your home and set the location, which drives sunrise and sunset automations.
- Choose your unit system and currency, then finish.
- Open Settings → System → Logs and confirm there are no repeating errors.
Your files now live in /opt/homeassistant. Open configuration.yaml there when you need to add integrations that are not in the UI.
Add remote access
Do not port-forward 8123 straight to the internet. Home Assistant serves plain HTTP by default, so credentials and session cookies would travel unencrypted.
Pick one of these instead:
- VPN first. A WireGuard or Tailscale tunnel keeps the dashboard private and needs no public port at all. This is the simplest safe option.
- Reverse proxy with HTTPS. Put Nginx Proxy Manager or Traefik in front, terminate TLS with a real certificate, and forward to
127.0.0.1:8123.
If you use a reverse proxy, tell Home Assistant to trust it. Add this to configuration.yaml and restart the container:
http:
use_x_forwarded_for: true
trusted_proxies:
- 127.0.0.1Use the proxy's actual source address. If the proxy runs in a bridge network rather than on the host, that address is the Docker bridge subnet instead. Check the proxy documentation for the exact value.
Warning: A wrong trusted_proxies list either blocks logins or lets clients spoof their IP. Verify the address before restarting.
Back up and update
Take a backup before every upgrade. Two layers are worth having:
- A Home Assistant snapshot from Settings → System → Backups, which you can restore from the UI.
- A file-level copy of
/opt/homeassistant, which survives a broken instance.
sudo tar czf /root/homeassistant-$(date +%F).tar.gz -C /opt homeassistantTo update, pull the new image and recreate the container:
cd /opt/homeassistant
docker compose pull
docker compose up -dWarning: Read the release notes before a major version bump. Integrations and YAML keys are sometimes deprecated, and a bad upgrade can leave the UI unreachable until you fix the config.
Check it works
Run these on the VPS:
docker ps --filter name=homeassistant
curl -I http://127.0.0.1:8123
docker logs --tail 50 homeassistantYou want the container listed as Up, an HTTP/1.1 200 or 302 response from curl, and a log tail without repeating tracebacks. Then load http://203.0.113.10:8123 from your own machine and log in.
Troubleshooting
- Browser cannot reach port 8123. The host firewall is blocking it. Check with
sudo ss -tlnp | grep 8123to confirm Home Assistant is listening, then allow the port inufw,firewalldor your provider's firewall. Do not open it publicly if you plan to use a VPN. - Container restarts in a loop. Almost always a YAML error in
configuration.yaml. Rundocker logs homeassistantand look for the line number; indentation and tabs are the usual culprits. - Zigbee or Z-Wave stick not detected. The device node was not passed through. Confirm it exists with
ls /dev/ttyUSB*, then add--device=/dev/ttyUSB0or keep--privileged. USB paths can change after a reboot, so consider a udev rule with a stable symlink. - Login redirects forever behind a proxy.
trusted_proxiesis missing or wrong. Set it to the proxy's real source address and restart the container. - Timestamps are wrong in automations. The
TZvariable does not match your location. Fix it in the run command or Compose file and recreate the container.
Next steps
- Put a proper HTTPS front end in front of the dashboard with Nginx Proxy Manager in Docker.
- Understand why host mode matters here by reading Docker networks: bridge, host and custom.
- Schedule off-site copies of the config directory with Back up Docker volumes with restic.
- Keep the image current without surprises using Update self-hosted containers safely with Watchtower.
