--- name: ha-addon description: "Develop Home Assistant add-ons with Docker, Supervisor API, and multi-arch builds. Use when creating add-ons, configuring Dockerfiles, setting up ingress, or publishing to repositories. Activates on keywords: add-on, addon, supervisor, hassio, ingress, bashio, docker." --- # Home Assistant Add-On Development > Expert guidance for building, configuring, and publishing Home Assistant add-ons with Docker, Supervisor integration, and multi-architecture support. ## Before You Start **This skill prevents common Home Assistant add-on development errors:** | Issue | Symptom | Solution | |-------|---------|----------| | Permission errors | `Permission denied` on supervisor API calls | Use correct SUPERVISOR_TOKEN and API endpoints | | Configuration validation | Add-on won't load | Validate config.yaml schema before publishing | | Docker base image errors | Missing dependencies in runtime | Use official Home Assistant base images (ghcr.io/home-assistant) | | Ingress misconfiguration | Web UI not accessible through HA | Configure nginx reverse proxy correctly | | Multi-arch build failures | Add-on only works on one architecture | Set up build.yaml with architecture matrix | ## Quick Start: Create an Add-On from Scratch ### Step 1: Create the Add-On Directory Structure ```bash mkdir -p my-addon/{rootfs,rootfs/etc/s6-overlay/s6-rc.d/service-name} cd my-addon ``` **Why this matters:** Home Assistant expects specific directory layouts. The `rootfs/` contains your actual application files that get packaged into the Docker image. ### Step 2: Create config.yaml ```yaml --- name: My Custom Add-On description: My awesome Home Assistant add-on version: 1.0.0 slug: my-addon image: ghcr.io/home-assistant/{arch}-addon-my-addon arch: - amd64 - armv7 - aarch64 ports: 8080/tcp: null options: debug: false schema: debug: bool permissions: - homeassistant # Read/write Home Assistant core data ``` **Why this matters:** This is your add-on's manifest. The slug becomes the internal identifier and determines where configuration is stored. ### Step 3: Create the Dockerfile ```dockerfile FROM ghcr.io/home-assistant/amd64-base:latest # Install dependencies RUN apk add --no-cache python3 py3-pip # Copy application COPY rootfs / # Set working directory WORKDIR /app # Install Python packages if needed RUN if [ -f requirements.txt ]; then pip install -r requirements.txt; fi # Run using S6 overlay CMD ["/init"] ``` **Why this matters:** Using Home Assistant base images includes critical runtime components (S6 overlay, bashio helpers, supervisor integration). ### Step 4: Create S6 Service Script Create `rootfs/etc/s6-overlay/s6-rc.d/service-name/run`: ```bash #!/command/execlineb -P foreground { echo "Starting my add-on..." } /app/my-service ``` Make it executable: ```bash chmod +x rootfs/etc/s6-overlay/s6-rc.d/service-name/run ``` **Why this matters:** S6 overlay is Home Assistant's init system. It manages service startup, logging, and graceful shutdown. ## Critical Rules ### ✅ Always Do - ✅ Use official Home Assistant base images (ghcr.io/home-assistant/{arch}-base) - ✅ Include all supported architectures in config.yaml (amd64, armv7, aarch64) - ✅ Use bashio helper functions for common operations (bashio::log::info, bashio::addon::option) - ✅ Validate config.yaml schema before releasing - ✅ Document configuration options in the schema section - ✅ Include addon_uuid in logs for debugging ### ❌ Never Do - ❌ Don't hardcode paths - use bashio to get configuration directory (/data/) - ❌ Don't run services as root unless absolutely necessary (set USER in Dockerfile) - ❌ Don't call supervisor API without SUPERVISOR_TOKEN - ❌ Don't ignore SIGTERM signals - implement graceful shutdown - ❌ Don't assume one architecture - use {arch} placeholder in image names - ❌ Don't store data outside /data/ - Home Assistant won't persist it ### Common Mistakes **❌ Wrong: Hardcoded paths** ```bash #!/bin/bash CONFIG_PATH="/config/my-addon" ``` **✅ Correct: Using bashio for configuration** ```bash #!/command/execlineb -P CONFIG_PATH=${"$(bashio::addon::config_path)"} ``` **Why:** bashio handles path resolution and ensures your add-on works in any Home Assistant installation. ## Configuration Reference ### config.yaml Structure ```yaml --- name: String # Display name description: String # Short description version: String # Semantic version (1.0.0) slug: String # URL-safe identifier image: String # Docker image URL with {arch} placeholder arch: - amd64|armv7|aarch64|armhf|i386 # Supported architectures ports: 8080/tcp: null # TCP port (null=internal only, number=external) 53/udp: 53 # UDP with external port mapping devices: - /dev/ttyACM0 # Device access services: - mysql # Depends on other service options: debug: false # User configuration options log_level: info schema: debug: bool # Configuration validation schema log_level: - debug - info - warning - error permissions: - homeassistant # Read/write HA config - hassio # Full supervisor API access - admin # Broad system access - backup # Backup/restore operations environment: NODE_ENV: production webui: http://[HOST]:[PORT:8080] # Web UI URL pattern ingress: true # Enable ingress proxy ingress_port: 8080 # Internal port for ingress ingress_entry: / # URL path for ingress entry ``` **Key settings:** - `slug`: Used internally and in supervisor API calls - `arch`: List all supported architectures or builds fail - `image`: Must use {arch} placeholder for dynamic builds - `options`: User-configurable settings - `permissions`: Controls supervisor API access level - `ingress`: Enables reverse proxy for web UIs ## Common Patterns ### Using bashio for Logging ```bash #!/command/execlineb -P foreground { bashio::log::info "Add-on started" } foreground { bashio::log::warning "Low disk space" } foreground { bashio::log::error "Failed to connect" } ``` ### Accessing Configuration Options ```bash #!/command/execlineb -P define DEBUG "$(bashio::addon::option 'debug')" define LOG_LEVEL "$(bashio::addon::option 'log_level')" if { test "${DEBUG}" = "true" } bashio::log::debug "Debug mode enabled" ``` ### Supervisor API Communication ```bash #!/bin/bash # Get addon info curl -X GET \ -H "Authorization: Bearer $SUPERVISOR_TOKEN" \ http://supervisor/addons/self/info | jq . # Send notification curl -X POST \ -H "Authorization: Bearer $SUPERVISOR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"message":"Warning message"}' \ http://supervisor/notifications/create ``` ### Multi-Arch Docker Build Create `build.yaml`: ```yaml build_from: amd64: ghcr.io/home-assistant/amd64-base:latest armv7: ghcr.io/home-assistant/armv7-base:latest aarch64: ghcr.io/home-assistant/aarch64-base:latest armhf: ghcr.io/home-assistant/armhf-base:latest codenotary: your-notary-id # Optional code signing ``` ### Ingress Configuration for Web UIs ```yaml ingress: true ingress_port: 8080 ingress_entry: / # Optional ingress_stream for streaming endpoints ``` Inside your app, use correct reverse proxy headers: ```bash # nginx configuration in your app location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $host; proxy_pass http://localhost:8080; } ``` ## Known Issues Prevention | Issue | Root Cause | Solution | |-------|-----------|----------| | Add-on fails to start | Missing S6 service files | Create `/etc/s6-overlay/s6-rc.d/service-name/` with `run` executable | | Supervisor API returns 401 | Invalid SUPERVISOR_TOKEN | Verify token is set by Home Assistant (check logs with `addon_uuid`) | | Configuration not persisting | Saving outside /data/ | Always use bashio::addon::config_path or /data/ for persistence | | Port already in use | Multiple services on same port | Check configuration - each service needs unique port | | Architecture mismatch | {arch} placeholder not used | Use exact placeholder in image field: `ghcr.io/home-assistant/{arch}-base` | | Build fails with "Unknown architecture" | config.yaml lists unsupported arch | Use only: amd64, armv7, aarch64, armhf, i386 | ## Supervisor API Endpoints ```bash # Authentication: Pass SUPERVISOR_TOKEN header # Base URL: http://supervisor GET /addons/self/info # Get current add-on details POST /addons/self/restart # Restart this add-on GET /addons/installed # List installed add-ons GET /info # System information POST /notifications/create # Send notification to user GET /config/homeassistant # Read Home Assistant config # Example with bashio bashio::addon::self_info # Helper function for self info ``` ## Dependencies ### Required | Package | Version | Purpose | |---------|---------|---------| | Home Assistant | 2024.1+ | Add-on platform and supervisor | | Docker | Latest | Container runtime | | S6 Overlay | 3.x | Init system (included in base images) | ### Optional | Package | Version | Purpose | |---------|---------|---------| | bashio | Latest | Helper functions (included in base images) | | python3 | 3.9+ | Python-based add-ons | | nodejs | 18+ | Node.js-based add-ons | ## Official Documentation - [Home Assistant Add-On Development](https://developers.home-assistant.io/docs/add-ons) - [Add-On Configuration Reference](https://developers.home-assistant.io/docs/add-ons/configuration) - [Supervisor Development](https://developers.home-assistant.io/docs/supervisor/developing) - [bashio Helper Functions](https://github.com/hassio-addons/bashio) - [S6 Overlay Documentation](https://skarnet.org/software/s6-overlay/) ## Troubleshooting ### Add-on Won't Start **Symptoms:** Add-on shows as "Not running" or "Unknown" **Solution:** ```bash # Check logs docker logs addon_name_latest # Or use HA UI: Settings > System > Logs # Common causes: # 1. Invalid config.yaml syntax # 2. Missing S6 service files # 3. Dockerfile can't find base image # 4. Permission denied on rootfs files ``` ### Supervisor API Returns 401 **Symptoms:** API calls fail with "Unauthorized" **Solution:** ```bash # Verify SUPERVISOR_TOKEN is set echo $SUPERVISOR_TOKEN # Check add-on logs for token errors # Token is automatically injected by Home Assistant # Verify permissions in config.yaml # If calling hassio endpoints, add: permissions: [hassio] ``` ### Configuration Not Saving **Symptoms:** Options are lost after restart **Solution:** ```bash # Always save to /data/ or use bashio CONFIG_PATH="$(bashio::addon::config_path)" # Returns /data/ echo "my_value=123" > "${CONFIG_PATH}/settings.json" # Verify /data/ exists and is writable ls -la /data/ ``` ### Ingress Web UI Not Accessible **Symptoms:** Ingress URL returns 502 or blank page **Solution:** ```bash # 1. Verify service is listening on correct port netstat -tlnp | grep 8080 # 2. Check reverse proxy headers in app config # X-Forwarded-For, X-Forwarded-Proto must be set # 3. Verify ingress settings in config.yaml ingress: true ingress_port: 8080 ingress_entry: / ``` ### Build Fails with Architecture Error **Symptoms:** "Unknown architecture" or "Image not found" **Solution:** ```yaml # Check config.yaml has valid arch values arch: - amd64 # x86 64-bit - armv7 # 32-bit ARM (Pi 2/3) - aarch64 # 64-bit ARM (Pi 4+) - armhf # 32-bit ARM (older devices) - i386 # 32-bit x86 (rare) # Dockerfile must use {arch} placeholder FROM ghcr.io/home-assistant/{arch}-base:latest ``` ## Setup Checklist Before publishing your add-on, verify: - [ ] config.yaml has valid YAML syntax (use online YAML validator) - [ ] All listed architectures are supported (amd64, armv7, aarch64, armhf, i386) - [ ] Dockerfile uses official Home Assistant base image - [ ] S6 service files exist and are executable (chmod +x) - [ ] All configuration options are documented in schema - [ ] No hardcoded paths (use bashio helpers) - [ ] Permissions field lists required supervisor API access - [ ] Tested on at least amd64 and ARM architecture - [ ] Logs use bashio::log functions - [ ] Graceful shutdown on SIGTERM implemented - [ ] /data/ used for all persistent data - [ ] Ingress working if web UI is provided - [ ] README includes installation and usage instructions ## Creating a Repository To publish multiple add-ons: ### 1. Create Repository Structure ```bash mkdir my-addon-repo cd my-addon-repo ``` ### 2. Create repository.yaml ```yaml --- name: My Add-On Repository url: https://github.com/username/my-addon-repo maintainer: Your Name ``` ### 3. Add Add-Ons ``` my-addon-repo/ ├── repository.yaml ├── my-addon-1/ │ ├── config.yaml │ ├── Dockerfile │ └── rootfs/ └── my-addon-2/ ├── config.yaml ├── Dockerfile └── rootfs/ ``` ### 4. Push to GitHub Add the repository URL to Home Assistant to make add-ons discoverable. ## Advanced: Publishing to GitHub Container Registry For private repositories or multi-architecture builds: ```bash # Build and push for all architectures docker buildx build \ --platform linux/amd64,linux/arm/v7,linux/arm64/v8 \ -t ghcr.io/username/my-addon:1.0.0 \ --push . ``` ## Related Skills - `docker-configs` - Docker fundamentals and best practices - `esphome-config-helper` - Related IoT device integration patterns - `home-assistant-automation` - Home Assistant automation and scripting