Home Assistant stack
Step-by-step guide to running Home Assistant, Node-RED, PostgreSQL, and pgAdmin with Docker Compose, including backups and recorder integration.
This guide shows how to deploy a clean Home Assistant stack with Node-RED, PostgreSQL (for history storage), pgAdmin (web UI for DB management), and automated backups — all managed via Docker Compose.
📂 Directory Layout
/opt/docker/homeassistant/
├── config/ # Home Assistant config
├── node-red/ # Node-RED data
├── postgres/ # Postgres data
├── pgadmin/ # pgAdmin config
├── backups/ # Database dumps
├── docker-compose.yml
├── .env
└── backup.sh
1) .env
# General
TZ=Europe/Amsterdam
BASE_PATH=/opt/docker/homeassistant
# Postgres
POSTGRES_DB=homeassistant
POSTGRES_USER=hass
POSTGRES_PASSWORD=change-me-to-a-strong-password
POSTGRES_PORT=5432
# pgAdmin
PGADMIN_DEFAULT_EMAIL=admin@example.com
PGADMIN_DEFAULT_PASSWORD=change-me-too
PGADMIN_PORT=5050
# Home Assistant recorder connection string
RECORDER_DB_URL=postgresql://hass:change-me-to-a-strong-password@127.0.0.1:5432/homeassistant
# Backups
# keep the last N dumps (by newest first)
BACKUP_RETENTION=14
2) docker-compose.yml
version: "3.9"
services:
homeassistant:
container_name: homeassistant
image: ghcr.io/home-assistant/home-assistant:stable
volumes:
- ${BASE_PATH}/config:/config
- /etc/localtime:/etc/localtime:ro
# Optional USB passthroughs:
# - /dev/ttyUSB0:/dev/ttyUSB0
# - /dev/ttyACM0:/dev/ttyACM0
environment:
- TZ=${TZ}
- RECORDER_DB_URL=${RECORDER_DB_URL}
restart: unless-stopped
privileged: true
network_mode: host
node-red:
container_name: node-red
image: nodered/node-red:latest
volumes:
- ${BASE_PATH}/node-red:/data
environment:
- TZ=${TZ}
restart: unless-stopped
network_mode: host
postgres:
container_name: hass-postgres
image: postgres:16
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
TZ: ${TZ}
volumes:
- ${BASE_PATH}/postgres:/var/lib/postgresql/data
- ${BASE_PATH}/backups:/backups
ports:
- "${POSTGRES_PORT}:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- dbnet
pgadmin:
container_name: pgadmin
image: dpage/pgadmin4:latest
environment:
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL}
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD}
TZ: ${TZ}
volumes:
- ${BASE_PATH}/pgadmin:/var/lib/pgadmin
ports:
- "${PGADMIN_PORT}:80"
depends_on:
- postgres
restart: unless-stopped
networks:
- dbnet
networks:
dbnet:
driver: bridge
3) backup.sh
#!/usr/bin/env bash
# Simple Home Assistant Postgres backup script
# - Reads .env next to docker-compose.yml
# - Creates timestamped dumps in ${BASE_PATH}/backups
# - Keeps the last ${BACKUP_RETENTION} dumps
set -euo pipefail
# Move to compose directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
# Export vars from .env for this shell
if [[ -f .env ]]; then
set -a
# shellcheck disable=SC1091
source .env
set +a
else
echo "ERROR: .env not found in $SCRIPT_DIR" >&2
exit 1
fi
# Sanity checks
: "${BASE_PATH:?BASE_PATH missing}"
: "${POSTGRES_DB:?POSTGRES_DB missing}"
: "${POSTGRES_USER:?POSTGRES_USER missing}"
: "${BACKUP_RETENTION:=14}"
BACKUP_DIR="${BASE_PATH}/backups"
CONTAINER="hass-postgres"
STAMP="$(date +%F_%H%M%S)"
FILENAME="ha_${STAMP}.dump"
TARGET_PATH="${BACKUP_DIR}/${FILENAME}"
mkdir -p "$BACKUP_DIR"
# Verify container is running
if ! docker ps --format '{{.Names}}' | grep -qx "$CONTAINER"; then
echo "ERROR: Container '$CONTAINER' not running." >&2
exit 2
fi
echo "Creating backup: ${TARGET_PATH}"
# Create dump inside container, writing to mounted /backups
docker exec -t "$CONTAINER" pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -F c -f "/backups/${FILENAME}"
# Quick integrity check: list dump TOC
docker exec -t "$CONTAINER" pg_restore -l "/backups/${FILENAME}" > /dev/null
# Prune older backups, keep newest BACKUP_RETENTION
echo "Pruning to last ${BACKUP_RETENTION} backups..."
ls -1t "${BACKUP_DIR}"/ha_*.dump 2>/dev/null | tail -n +$((BACKUP_RETENTION+1)) | xargs -r rm -f --
echo "Done."
Make it executable:
chmod +x ${BASE_PATH}/backup.sh
Optional cron (runs daily at 03:00):
0 3 * * * cd /opt/docker/homeassistant && ./backup.sh >> /opt/docker/homeassistant/backup.log 2>&1
4) Home Assistant Recorder Config
In ${BASE_PATH}/config/configuration.yaml:
recorder:
db_url: !env_var RECORDER_DB_URL
commit_interval: 5
auto_purge: true
purge_keep_days: 14
# exclude:
# domains: [updater]
# entities: [sensor.date]
5) Restore from Backup
Pick a dump (e.g., ha_2025-09-13_030000.dump) from ${BASE_PATH}/backups:
# Drop & recreate DB
docker exec -i hass-postgres psql -U "$POSTGRES_USER" -d postgres <<'SQL'
SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'homeassistant';
DROP DATABASE IF EXISTS homeassistant;
CREATE DATABASE homeassistant OWNER hass;
SQL
# Restore from dump
docker exec -i hass-postgres pg_restore -U "$POSTGRES_USER" -d "$POSTGRES_DB" --clean --if-exists < ${BASE_PATH}/backups/ha_2025-09-13_030000.dump
✅ With this setup you get:
- Home Assistant + Node-RED on host network
- PostgreSQL with persistent storage
- pgAdmin to inspect/manage your DB
- Automated backup rotation
- Easy relocation by editing one
BASE_PATHvariable in.env