Installation

Get a working myHAB in about twenty minutes — Docker for a real install, Gradle for development.

Just want to look around?

Don't install anything. demo.myhab.org is a complete, fully interactive installation with simulated devices.

Prerequisites

ComponentVersionWhy
PostgreSQL14 or newerEntities, partitioned time series, Quartz tables.
MQTT brokerMosquitto 2.x or equivalentEvery device command and state echo.
A git repositoryany host, or a bare repo on diskRuntime configuration store, read by ConfigProvider.
Java17 (Temurin)Only if you build or run from source; the Docker image bundles a JRE.
Node.js + YarnNode 18+Only for frontend development.

Hardware: myHAB is comfortable on a small x86 box or a Raspberry Pi 4/5 class machine with 2 GB of RAM for the JVM. The database is the part that grows — port values accumulate quickly if you poll a lot of sensors.

Step 1 — Create the configuration repository

myHAB reads its runtime configuration from a git repository, not from a file inside the container. This is what lets you change MQTT credentials, feature flags or dashboard bindings without rebuilding or restarting anything, and gives you a history of every change.

Create a repository (private — it will hold credentials), with one branch per environment. The default branch name in a production deployment is prod.

mkdir myhab-config && cd myhab-config
git init -b prod
cat > config.yaml <<'YAML'
mqtt:
  hostname: mosquitto
  port: 1883
  username: myhab
  password: change-me
  topics: myhab/#

cors:
  allowedOrigin:
    - https://home.example.com

heat:
  thermostat:
    enabled: false
  temp:
    allDay: 21

admin:
  devices:
    autoimport: false
  ports:
    autoimport: false

ui:
  meteo:
    locationName: My Town
YAML
git add . && git commit -m "Initial myHAB configuration"
git push origin prod
Treat it as a secret store.

The appConfig GraphQL query returns every key to any admin-authenticated caller, and secrets committed to git stay in its history. Rotate by issuing a new credential and revoking the old one — not by overwriting the value.

If you would rather not host a git server, a bare repository on a mounted volume works — point CFG_REPO_URI at file:///config-repo/myhab-config.git. That is exactly what the public demo does.

Step 2 — Run it with Docker Compose

The published image is kirpi4ik/myhab:<version> on Docker Hub, built from eclipse-temurin:17-jre. It serves both the API and the compiled web client on port 8181.

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_DB: myhab
      POSTGRES_USER: myhab
      POSTGRES_PASSWORD: change-me
    volumes: [ "pgdata:/var/lib/postgresql/data" ]
    restart: unless-stopped

  mosquitto:
    image: eclipse-mosquitto:2
    volumes:
      - ./mosquitto.conf:/mosquitto/config/mosquitto.conf:ro
      - "mqttdata:/mosquitto/data"
    ports: [ "1883:1883" ]          # devices on the LAN connect here
    restart: unless-stopped

  myhab:
    image: kirpi4ik/myhab:2.8.12
    depends_on: [ db, mosquitto ]
    environment:
      GRAILS_ENV: production
      TZ: UTC
      DB_URL: jdbc:postgresql://db:5432/myhab
      DB_USERNAME: myhab
      DB_PASSWORD: change-me
      JWT_SECRET: "<a long random string>"
      CFG_REPO_URI: https://git.example.com/you/myhab-config.git
      CFG_USERNAME: myhab-bot
      CFG_PASSWORD: "<token>"
    ports: [ "8181:8181" ]
    volumes:
      - ./config:/app/config:ro     # optional Spring Boot overrides, see below
    restart: unless-stopped

volumes:
  pgdata:
  mqttdata:
Timezone.

Run the container in UTC. myHAB stores every timestamp in UTC and converts for display using each user's timezone preference; running the JVM in a local zone produces charts that shift twice a year.

Bring it up and watch the first boot:

docker compose up -d
docker compose logs -f myhab

GORM creates the schema on first start (dbCreate = update). The application answers on http://<host>:8181/, and a health endpoint is available at /actuator/healthcheck.

Step 3 — Create the first account

myHAB does not seed a default administrator — there is no well-known password to forget to change. Insert one directly, with a BCrypt hash of your chosen password.

# generate a hash (any bcrypt tool works)
htpasswd -bnBC 10 "" 'your-password' | tr -d ':\n'
INSERT INTO sec_roles (id, version, authority) VALUES
  (1, 0, 'ROLE_USER'),
  (2, 0, 'ROLE_ADMIN')
ON CONFLICT DO NOTHING;

-- note: `users` has no version column
INSERT INTO users (id, username, password, first_name, last_name, email,
                   enabled, account_locked, account_expired, password_expired,
                   ts_created, ts_updated, en_type, language, timezone)
VALUES (1, 'admin', '<bcrypt hash>', 'Site', 'Admin', 'admin@example.com',
        true, false, false, false, now(), now(), 'USER', 'en', 'UTC');

INSERT INTO sec_user_roles (user_id, role_id) VALUES (1, 1), (1, 2);
Use sec_user_roles, not users_sec_roles.

Spring Security resolves authorities through the former. The similarly-named users_sec_roles table is a legacy join; writing to it leaves the account with ROLE_NO_ROLES and a login that succeeds but can do nothing.

Log in, then create the rest of your users from Infrastructure → Users.

Step 4 — Add your first device

  1. Infrastructure → Devices → Add device. Give it a code — this is what appears in its MQTT topics.
  2. Pick the model (ESP, MegaD, or a virtual model for a cloud integration) and assign it to a zone.
  3. Add ports for each I/O line, or let the controller's own announcements create them if admin.ports.autoimport is enabled in configuration.
  4. Create a peripheral (“Terrace light”), connect it to the port, and place it in a zone. It now appears on the dashboard.
  5. Optionally document the cable between them and print a QR label.
Device codes must not contain hyphens.

Outbound command topics are built from a template, but inbound state topics are matched with myhab/(\w+|_+)/(\w+|_+)/(\w+|_+)/state, and \w excludes -. A hyphenated code publishes commands happily and then silently drops every echo: the control looks dead, with nothing in the logs. Use esp_terrace, not esp-terrace.

Step 5 — Put it behind a reverse proxy

myHAB was written for a private LAN behind a router. If you expose it to the internet, terminate TLS at a proxy you control, and tell the application which proxy to believe:

# ./config/application.yml — layered over the image's defaults by Spring Boot
myhab:
  security:
    # The proxy's own address. Empty (the default) means no forwarded header is
    # trusted at all — restrictive, not permissive.
    trustedProxies:
      - 172.18.0.5

At the proxy itself:

Building from source

The repository is a multi-module Gradle build. Java 17 is required; the Gradle wrapper is included.

git clone https://github.com/kirpi4ik/myhab.git
cd myhab

./gradlew bootRun                 # backend on :8181
./gradlew serve                   # Quasar dev server on :10002
./gradlew test                    # Spock test suite

./gradlew assembleServerAndClient # one JAR containing server + compiled client
./gradlew buildImage              # local Docker image

Frontend work happens in client/web-vue3:

cd client/web-vue3
yarn install
yarn serve        # dev server, port 10002
yarn pwa:build    # production PWA build
yarn lint && yarn format
ModulePathContains
:server-coreserver/server-coreControllers, GORM domain, services, Quartz jobs, GraphQL.
:server-configserver/server-libs/server-configConfigProvider and the configuration-key constants.
:server-rulesserver/server-libs/server-rulesHeating and lighting rule facts.
:web-vue3client/web-vue3Vue 3 + Quasar PWA.
:demo-simulatordemo/simulatorMQTT device simulator used by the public demo.
The Android client is a separate build.

client/android/ has its own Gradle wrapper and is deliberately excluded from the root settings.gradle — the Android Gradle Plugin and the Grails plugin do not coexist in one build. Open only client/android in Android Studio. See the voice documentation.

Running the demo sandbox locally

The demo environment is a self-contained dataset with simulated devices — the quickest way to see a populated installation without owning any hardware. No Docker required.

./gradlew demoSeedLocal   # (re)build the myhab_demo database — destructive, idempotent
./gradlew demoSim         # simulator + embedded MQTT broker on :1883
./gradlew demoRun         # backend in the `demo` environment
./gradlew serve           # Quasar dev server on :10002

Open http://localhost:10002 and sign in with demo / demo. Start demoSim before demoRun — the application connects to the broker at boot and the simulator is hosting it. More detail on the demo page.

Upgrading

  1. Back up the database. dbCreate = update adds columns and tables but never removes anything, so a rollback means restoring a dump.
  2. Pull the new tag and recreate the application container. The schema migrates on start.
  3. Check the release notes for any manual SQL under doc/migrations/ in the repository.
  4. Configuration lives in git, so it survives the upgrade untouched.

Troubleshooting first boot

SymptomLikely cause
Application starts, login page loads, credentials rejectedNo user rows, or roles written to users_sec_roles instead of sec_user_roles.
Login succeeds but every screen is emptyThe account has no ROLE_ADMIN.
Controls render but nothing switchesNothing is answering on MQTT, or the device code contains a hyphen so state echoes never match.
Startup fails resolving configurationCFG_REPO_URI unreachable, wrong credentials, or the branch does not exist (prod by default in production).
Browser console shows CORS errorsAdd your origin to cors.allowedOrigin in the configuration repository.
Charts shifted by hoursThe container is not running in UTC.
Audit shows the proxy's IP for every actionmyhab.security.trustedProxies is unset.