---
title: Home Assistant
description: Running Home Assistant Core and frontend development servers through dot, pitchfork and Herdr.
---

I work on [Home Assistant Core](https://github.com/home-assistant/core) and the [frontend](https://github.com/home-assistant/frontend) most days. `dot homeassistant` (`dot ha`) sets up each repository, then runs its dev server as a [pitchfork](https://pitchfork.jdx.dev) daemon so it gets a stable HTTPS URL from pitchfork's proxy. Under [Herdr](/desktop/herdr/), each command opens its own tab in the `[HA] Core` or `[HA] Frontend` workspace.

The short `hacdev`, `hafdev*` and `hadev*` functions in `.zshrc` just call `dot ha`. Repository paths, workspaces, daemon names, URLs and serve targets live in the private `~/.config/dot/homeassistant.yml`, and the pitchfork config lives in the private overlay too, so it is copied below with real URLs replaced.

## Commands

| Command | Alias |
| --- | --- |
| `dot homeassistant` | `dot ha` |
| `dot homeassistant core` | `dot ha c` |
| `dot homeassistant frontend` | `dot ha f` |

| Case | Command | Shortcut |
| --- | --- | --- |
| What is running, and where | `dot ha status [target...]` | `hadevstatus`, `hacdevstatus`, `hafdevstatus`, `hafdevservestatus`, `hafdevsuitesstatus` |
| Logs | `dot ha logs <target> [--follow] [--lines <n>]` | `hacdevlogs`, `hafdevlogs`, `hafdevservelogs`, `hafdevdesignlogs`, `hafdevdemologs`, `hafdeve2eapplogs` |
| Core with the local frontend build | `dot ha core dev [--background]` | `hacdev`, `hacdevbg` |
| Core, rebased onto `upstream/dev` first | `dot ha core dev --latest [--background]` | `hacdevlatest`, `hacdevlatestbg` |
| Core setup (Git, venv, bootstrap) | `dot ha core setup [--latest]` | Run by `hacdev*` |
| Frontend watch build alone | `dot ha frontend dev [--background] [--attach]` | `hafdev`, `hafdevbg` |
| Frontend against another Core | `dot ha frontend serve [<target or URL>] [--background]` | `hafdevserve` |
| Frontend against prod | `dot ha frontend serve prod [--background]` | `hafdevserveprod`, `hafdevserveprodbg` |
| Frontend through Home Assistant Link | `dot ha frontend serve link [--background]` | `hafdevserveprodnc`, `hafdevserveprodncbg` |
| Gallery | `dot ha frontend gallery [--background]` | `hafdevdesign`, `hafdevdesignbg` |
| Demo | `dot ha frontend demo [--background]` | `hafdevdemo`, `hafdevdemobg` |
| E2E test app | `dot ha frontend e2e [--background]` | `hafdeve2eapp`, `hafdeve2eappbg` |
| Production build | `dot ha frontend build` | `hafbuild` |
| E2E tests | `dot ha frontend test-e2e [app\|demo\|gallery]` | None |
| Core and frontend in Herdr tabs | `dot ha dev [--background]` | `hadev`, `hadevbg` |
| Stop servers (all by default) | `dot ha stop [target...]` | `hadevstop`, `hacdevstop`, `hafdevstop`, `hafdevservestop`, `hafdevdesignstop`, `hafdevdemostop`, `hafdeve2eappstop`, `hafdevsuitesstop` |

| Target | Daemon | URL |
| --- | --- | --- |
| `core` | `ha-core/dev` | `https://dev.ha-core.localhost` |
| `build` | `ha-frontend/build` | Through Core's URL |
| `serve` | `ha-frontend/serve` | `https://serve.ha-frontend.localhost` |
| `prod` | `ha-core/prod` | `https://prod.ha-core.localhost` |
| `gallery`, `demo`, `e2e` | The frontend's own background mode | Printed when started |

Port 443 already reaches pitchfork's proxy (listening on 8443), so the URLs need no port. The proxy builds each hostname from the daemon name and namespace. Its auto-start is off, so a stopped daemon's URL shows pitchfork's "No daemon found" page rather than starting it; only the commands start daemons.

## How the commands behave

- Interactive runs go through `dot status-run`, which pins a header with the state, URL and elapsed time, and keeps the terminal title in step.
- Each runs its setup first: `dot ha core setup` for Core (Git, virtual environment, `script/bootstrap`), `script/bootstrap` for the frontend.
- If the daemon is already running, it asks before restarting it. `dot ha dev`'s Frontend tab follows a running build instead.
- `--background` starts the daemon and returns once it's ready; otherwise it follows the logs, and Ctrl+C stops it.
- Every `dot ha frontend` command takes the frontend's build lock, so it asks to stop the `build` and `serve` daemons first. Lint, format, type checks and unit tests don't take the lock and stay plain `pnpm`.

:::note
Pitchfork doesn't pass the caller's environment to a daemon. `dot ha f serve` writes the Core URL to `~/.local/state/dot/homeassistant/serve.args`, and the `serve` daemon reads it when it starts.
:::

## Core behind the proxy

The proxy sets `Host` to `localhost:<port>` and adds `X-Forwarded-*` headers. Core rejects those unless it trusts the proxy. In the dev Core, turn on **Trust X-Forwarded-For** and add `127.0.0.1/32` and `::1/128` to **Trusted proxies**, under **Settings > System > Network > Reverse proxy**.

Serving against a remote Core also needs `https://serve.ha-frontend.localhost` in that instance's **CORS allowed origins**, on the same page.

[![Open your Home Assistant instance and show your network configuration.](https://my.home-assistant.io/badges/network.svg)](https://my.home-assistant.io/redirect/network/)

The browser blocks an HTTPS page from calling a plain HTTP Core, so the login works but the frontend can't connect afterwards. `ha-core/prod` fronts the prod instance on an HTTPS URL with `dot http-forward`, which drops the proxy's `X-Forwarded-*` headers so the remote Core accepts the requests without trusting this machine as a proxy. `dot ha f serve prod` starts it before `serve`.

Core only registers the frontend routes if `hass_frontend` exists when it starts, and a fresh build briefly removes it. `ha-core/dev` depends on `ha-frontend/build`, so starting Core starts the build too and waits for it to be ready.

## Agents

Agents use the same `dot ha` commands. Under an agent, `dot ha` skips setup, Herdr tabs and prompts: it reuses a running daemon, runs suites in the background, and fails with a message instead of stopping a conflicting daemon. `dot ha core setup` and `dot ha logs --follow` refuse to run. The Home Assistant workspace `AGENTS.md` tells agents not to run `hass`, `pnpm dev`, `pnpm dev:serve` or `pnpm build` directly, and to ask before stopping or swapping a running daemon.

## Pitchfork config

`~/.config/pitchfork/config.toml` enables the proxy and registers both repositories as namespaces:

```toml
[daemons]

[settings.proxy]
auto_start = false
enable = true
https = true
port = 8443

[namespaces.ha-core]
dir = "/home/<user>/repos/home-assistant/core"
config = ["/home/<user>/.config/dotfiles-private/pitchfork/.config/pitchfork/home-assistant/core.toml"]

[namespaces.ha-frontend]
dir = "/home/<user>/repos/home-assistant/frontend"
config = ["/home/<user>/.config/dotfiles-private/pitchfork/.config/pitchfork/home-assistant/frontend.toml"]
```

`home-assistant/core.toml`:

```toml
[daemons.dev]
run = "exec .venv/bin/hass -c config"
depends = ["ha-frontend/build"]
port = 8123
ready_port = { port = 8123, timeout = "5m" }
retry = true
stop_signal = { signal = "SIGTERM", timeout = "60s" }

[daemons.prod]
run = "exec dot http-forward --port 8126 --target http://homeassistant.local:8123"
port = 8126
ready_port = 8126
```

`home-assistant/frontend.toml`:

```toml
[daemons.build]
run = "exec pnpm dev --fetch-translations"
ready_output = { pattern = "Build done", timeout = "10m" }

[daemons.serve]
run = """
args="${XDG_STATE_HOME:-$HOME/.local/state}/dot/homeassistant/serve.args"
eval "set -- $(cat "$args" 2>/dev/null)"
exec pnpm dev:serve --fetch-translations "$@"
"""
port = 8124
ready_port = { port = 8124, timeout = "10m" }
```

## dot config

`~/.config/dot/homeassistant.yml`:

```yaml
core:
  directory: ~/repos/home-assistant/core
  workspace: "[HA] Core"
  daemon: ha-core/dev
  url: https://dev.ha-core.localhost
frontend:
  directory: ~/repos/home-assistant/frontend
  workspace: "[HA] Frontend"
  build: ha-frontend/build
  serve:
    daemon: ha-frontend/serve
    url: https://serve.ha-frontend.localhost
    targets:
      prod:
        title: Prod
        url: https://prod.ha-core.localhost
        daemon: ha-core/prod
      link:
        title: Link
        url: https://<instance>.ui.nabu.casa
```
