Docker-based Home Assistant on Ubuntu is the collective's recommended installation pattern for agricultural monitoring. Home Assistant runs as a container on top of Ubuntu Server, sharing the machine with other useful services — Mosquitto for MQTT, MariaDB for the history database, ESPHome for custom device management, Zigbee2MQTT if the operation uses Zigbee, Frigate if cameras with object detection are deployed, and others as needed. All of this is managed through Docker Compose, a tool that describes the full stack in a single configuration file. This is the graybox pattern: one capable machine running the full monitoring and control stack, under the grower's control. This page covers the installation from a fresh Ubuntu Server through a working Home Assistant with a minimal graybox alongside it. The grower following along ends with a running Home Assistant ready for its first integrations.
Before starting.
Prerequisites:
Ubuntu Server installed and updated. Per [Installing Ubuntu Server](/home-assistant/installation/ubuntu). The server should be reachable via SSH from the administration computer, with the grower's user account able to run `sudo`.
Static IP or DHCP reservation configured. The Home Assistant host needs a consistent network address. This was covered in the Ubuntu install page.
Hardware appropriate for the graybox. Per [Choosing Your Hardware](/home-assistant/hardware/choosing), a repurposed business desktop, repurposed laptop, or mini PC with at least 16 GB RAM and 256 GB SSD. Tier-3 hardware (Raspberry Pi, Home Assistant Green) is not appropriate for the full graybox pattern — those deployments should use [Installing Home Assistant OS](/home-assistant/installation/haos) instead.
Time. Allow 60 to 90 minutes for a first-time install. Subsequent graybox deployments go faster as the pattern becomes familiar.
Why Docker and Docker Compose.
Docker packages software into isolated containers. Each container includes everything the software needs to run — the application, its libraries, its configuration — without conflicting with other software on the same host. A grower running Home Assistant in a Docker container can run five other services in other containers on the same host, and updates to any one of them do not affect the others.
Docker Compose describes multi-container applications in a single YAML file (`docker-compose.yml`). Instead of running individual `docker run` commands for each service, the grower writes one file that defines the full stack and starts it all with `docker compose up -d`. The same file stops the stack, updates containers, shows status, and captures the full configuration for version control and backup.
For the graybox pattern specifically, Compose is the right tool because the services (Home Assistant, Mosquitto, MariaDB, and others) need to communicate with each other, share data volumes, and start in a coordinated way. Compose handles all of that with minimal ceremony.
The alternative — Home Assistant Supervised on Ubuntu — provides a similar outcome with a different approach. Supervised is Home Assistant with its full Supervisor running on Ubuntu, giving the grower the same add-on management experience as Home Assistant OS while keeping Ubuntu underneath. The tradeoff: Supervised is more opinionated (specific Ubuntu versions, specific host configuration) and runs its own container management rather than integrating cleanly with Docker Compose. For growers who want the Supervisor experience, [Installing Home Assistant Supervised](/home-assistant/installation/supervised) is the appropriate path. For growers who prefer direct Docker Compose control, this page is the path.
Installing Docker.
Docker Engine for Ubuntu is installed from Docker's official repository, not from Ubuntu's default packages (the version in Ubuntu's repository is usually older).
After installation, verify Docker works:
```bash docker --version docker compose version sudo docker run hello-world ```
The `hello-world` container prints a success message if Docker is working.
Add the current user to the docker group. Running Docker commands with `sudo` every time is tedious. Adding the user to the `docker` group allows the user to run Docker commands directly:
```bash sudo usermod -aG docker $USER ```
Log out and back in for the group change to take effect. After logging back in:
```bash docker ps ```
Should run without `sudo` and show an empty container list (no containers running yet).
Security note: Membership in the `docker` group is equivalent to root access on the host, because Docker containers can access the host's kernel and file system. The grower's user with Docker group membership is a privileged account. Keep the password strong, use SSH key authentication, and treat SSH access to this machine as administrative access.
Directory structure for the graybox.
The collective's recommended directory structure keeps the Docker Compose configuration and the service data organized under one path.
``` /opt/graybox/ ├── docker-compose.yml ├── .env ├── homeassistant/ │ ├── config/ │ └── ... ├── mosquitto/ │ ├── config/ │ ├── data/ │ └── log/ ├── mariadb/ │ └── data/ ├── esphome/ │ └── config/ ├── backups/ └── ... ```
The `docker-compose.yml` at the root defines the full stack. Each service has its own subdirectory for persistent data. `.env` stores environment variables and secrets (passwords, API keys) that should not be hardcoded in `docker-compose.yml`.
Create the directory structure:
```bash sudo mkdir -p /opt/graybox sudo chown -R $USER:$USER /opt/graybox cd /opt/graybox mkdir -p homeassistant/config mkdir -p mosquitto/config mosquitto/data mosquitto/log mkdir -p mariadb/data mkdir -p esphome/config mkdir -p backups ```
The grower owns the directory, which simplifies file management without needing `sudo` for every operation.
Some growers prefer a different location (`/srv/graybox`, `/home/grower/graybox`, or similar). The specific path is a matter of preference; the structure is what matters.
The base docker-compose.yml.
A minimal starting `docker-compose.yml` for the graybox includes Home Assistant, Mosquitto, and MariaDB.
Key elements the generated file should include:
Home Assistant service. Image `homeassistant/home-assistant:stable` (or specific version). Network mode `host` — required for some Home Assistant integrations (mDNS discovery, BLE, and others). Volume mount from `./homeassistant/config` to `/config` inside the container. Privileged mode if the host will use USB devices (Zigbee sticks, Bluetooth adapters). Restart policy `unless-stopped`. Environment variables for timezone (`TZ=America/Chicago` or similar).
Mosquitto service. Image `eclipse-mosquitto:2` (or current version). Port mappings for `1883` (MQTT) and `9001` (MQTT over WebSockets, optional). Volume mounts for `./mosquitto/config`, `./mosquitto/data`, `./mosquitto/log`. Restart policy `unless-stopped`. Requires a `mosquitto.conf` file in `./mosquitto/config/` — the Compose file references it.
MariaDB service. Image `mariadb:lts` (or `mariadb:11` for current version). Environment variables for root password and Home Assistant database user (pulled from `.env`). Volume mount for `./mariadb/data`. Restart policy `unless-stopped`. Port `3306` exposed internally (not published to the host).
Secrets in .env. The `.env` file contains values like: ``` MARIADBROOTPASSWORD=
The `.env` file should have restrictive permissions (`chmod 600`) and should never be committed to public version control if the grower uses git for the configuration.
Mosquitto configuration.
Mosquitto requires a configuration file at `./mosquitto/config/mosquitto.conf`. A minimal working configuration:
After creating the configuration file, create a Mosquitto user for Home Assistant's MQTT connection:
```bash docker compose run --rm mosquitto mosquitto_passwd -c /mosquitto/config/passwd homeassistant ```
The command prompts for a password, which Home Assistant will use when connecting to MQTT. Save the password in a password manager.
Starting the stack.
With `docker-compose.yml`, `.env`, and `mosquitto.conf` in place:
```bash cd /opt/graybox docker compose up -d ```
`up` creates and starts the containers. `-d` runs them in the background (detached).
The first run downloads the container images, which takes a few minutes on a decent internet connection. Subsequent runs start immediately because the images are cached locally.
Check that all containers are running:
```bash docker compose ps ```
All services should show `running` or `healthy`. If any show `exited` or `restarting`, check the logs:
```bash docker compose logs homeassistant docker compose logs mosquitto docker compose logs mariadb ```
Common first-run issues include: permission problems on volume mounts (fix with `sudo chown` on the relevant directory), missing configuration files (Mosquitto in particular needs `mosquitto.conf` present), and environment variables not loading from `.env` (check for typos in the file).
First access to Home Assistant.
Home Assistant takes a few minutes to start on the first run — it initializes its database and configuration. After a few minutes, navigate to `http://graybox-ip:8123` in a web browser (replace `graybox-ip` with the host's IP address, or use `homeassistant.local:8123` if mDNS works on the network).
The Home Assistant onboarding screen appears. Walk through the steps:
1. Create the admin account (name, username, password). 2. Provide location, timezone, elevation, and unit preferences. Home Assistant uses location for sun calculations, weather integrations, and local time display. 3. Decide whether to share anonymized usage statistics with the Home Assistant project (optional; the collective recommends enabling it — it helps the project prioritize development). 4. Home Assistant scans the local network for discoverable devices. The grower can configure some of them now or later.
Onboarding is complete. The default dashboard appears. The installation is working.
Configuring Home Assistant to use MariaDB.
By default, Home Assistant stores history in a local SQLite database. For a graybox deployment, switching to MariaDB improves performance and allows longer retention. Edit `./homeassistant/config/configuration.yaml` and add:
Before this configuration takes effect, the MariaDB database needs to exist. Connect to MariaDB and create it:
```bash docker compose exec mariadb mariadb -u root -p ```
Enter the MariaDB root password from `.env`. At the MariaDB prompt:
```sql CREATE DATABASE homeassistant CHARACTER SET utf8mb4; CREATE USER 'homeassistant'@'%' IDENTIFIED BY 'the-password-from-env'; GRANT ALL PRIVILEGES ON homeassistant.* TO 'homeassistant'@'%'; FLUSH PRIVILEGES; EXIT; ```
Restart Home Assistant to pick up the new recorder configuration:
```bash docker compose restart homeassistant ```
The Home Assistant logs (`docker compose logs homeassistant`) should show successful connection to MariaDB. The history going forward stores in MariaDB rather than SQLite.
Adding ESPHome.
ESPHome compiles firmware for ESP32 and ESP8266 devices used as custom sensors and controls. Adding it to the graybox:
After adding the ESPHome service definition to `docker-compose.yml`:
```bash docker compose up -d ```
The ESPHome web interface is then available at `http://graybox-ip:6052`. [ESPHome integration](/home-assistant/integrations/esphome) covers the specifics of using it for agricultural devices.
Adding MQTT to Home Assistant.
With Mosquitto running and a Home Assistant MQTT user configured, add the MQTT integration in Home Assistant's interface:
Settings → Devices & Services → Add Integration → MQTT.
Enter the connection details: - Broker: the graybox's IP address (or `localhost` if Home Assistant is running with `network_mode: host`) - Port: 1883 - Username: the Mosquitto user created above - Password: the password for that user
Home Assistant connects to the broker. MQTT is now available for other integrations and for ESPHome devices reporting state.
Adding additional services.
The graybox pattern extends as the operation requires. Common additions:
Zigbee2MQTT. For Zigbee device management. Requires a Zigbee coordinator USB stick. [Zigbee integration](/home-assistant/integrations/zigbee) covers the setup.
Frigate. For camera-based object detection. Benefits substantially from Intel Quick Sync (on most Intel CPUs) or a Coral USB AI accelerator. [Frigate and Computer Vision](/home-assistant/ai/frigate) covers the setup.
InfluxDB and Grafana. For time-series analytics alongside Home Assistant. [Grafana Integration](/home-assistant/dashboards/grafana) covers the pattern.
Node-RED. For flow-based automation logic. An alternative and complement to native Home Assistant automations.
Ollama. For local AI capabilities without cloud dependencies. [LLM Integrations](/home-assistant/ai/llm) covers the setup.
Nextcloud. For file sync, including automated backups of sensor data exports.
WireGuard or Tailscale. For secure remote access. [Remote Access](/home-assistant/operations/remote-access) covers the options.
Each additional service gets its own section in `docker-compose.yml` and typically its own subdirectory under `/opt/graybox/`. The pattern is the same: define the service, mount its persistent data directory, set a restart policy, run `docker compose up -d`.
Updating the stack.
Updating Home Assistant and the other services is a short operation with Docker Compose:
```bash cd /opt/graybox docker compose pull # download newer images docker compose up -d # recreate containers with new images docker image prune # remove old unused images (optional cleanup) ```
The `pull` command downloads newer versions of any images that have been updated. The `up -d` command recreates the containers using the new images, preserving the persistent data in the mounted volumes. The whole process takes a few minutes and results in updated services.
For production deployments, the collective recommends testing updates before applying them to the working graybox. The [Updates and Version Management](/home-assistant/operations/updates) page covers the practices.
Backing up the graybox.
The graybox pattern's persistent data lives in `/opt/graybox/`. Backing up that directory captures the entire state of the graybox — Home Assistant's configuration and database, Mosquitto's persistence file, MariaDB's database, ESPHome's configurations, and anything else in the tree.
A simple daily backup pattern:
The backup script can be triggered by cron, a systemd timer, or manual invocation. The [Backup and Recovery](/home-assistant/operations/backup) page covers the strategy in depth — local backups, offsite copies, tested recovery, and what to do when things go wrong.
Common issues.
A few problems come up frequently on first deployment.
Permission denied on volume mounts. Docker containers often run as specific users inside the container. When the host directory is owned by a different user, the container cannot write to it. Fix by setting the directory owner to match the container's expected UID (often 1000), or use `chmod` to allow the container's user access.
Home Assistant cannot find devices. If `networkmode: host` is not set, Home Assistant's network discovery (mDNS, auto-discovery) does not find devices on the local network. For most graybox deployments, `networkmode: host` is the right setting.
MariaDB connection refused. If the `recorder` configuration uses a container name or IP that Home Assistant cannot reach, the connection fails. With `network_mode: host` on Home Assistant, the database connection uses `localhost:3306` (since MariaDB exposes its port to the host).
Containers restart continuously. A container that keeps crashing usually logs the error. `docker compose logs
Home Assistant not visible at `http://graybox-ip:8123`. Confirm the container is running (`docker compose ps`), confirm the firewall allows port 8123 (`sudo ufw status`, add rule if needed), confirm Home Assistant is actually listening on that port (`docker compose logs homeassistant` should show the web server starting).
Updates fail or break functionality. Home Assistant's release notes document breaking changes. Before a major update, read the release notes. The collective recommends testing updates in a backup or virtual machine first, especially for updates flagged as containing breaking changes.