Home Assistant Add-on Creator (from Docker Images)
Specialized skill for creating Home Assistant add-ons from existing Docker images with automatic ingress (embedded web UI) support.
What This Skill Does
This skill helps you:
- •Analyze Docker images - Use the discovery script to extract configuration from existing Docker images
- •Scaffold add-on structure - Generate complete add-on directory with all required files
- •Configure ingress - Set up nginx reverse proxy for embedded Home Assistant web UI
- •Apply best practices - Follow Home Assistant add-on standards with s6-overlay v3 process supervision
When to Use This Skill
Use this skill when:
- •Converting an existing Docker application to a Home Assistant add-on
- •Creating an add-on with a web interface that should appear in the Home Assistant sidebar
- •Wrapping third-party containerized services for Home Assistant
- •Building add-ons that need ingress (embedded UI) support
- •Analyzing a Docker image to understand its structure for add-on creation
Quick Start Workflow
1. Discover Docker Image Configuration
First, analyze the target Docker image to understand its structure:
# Run the discovery script bash .claude/skills/ha-apps/scripts/discover.sh <docker-image> # Examples: bash .claude/skills/ha-apps/scripts/discover.sh linuxserver/plex:latest bash .claude/skills/ha-apps/scripts/discover.sh ghcr.io/user/myapp:v1.0 bash .claude/skills/ha-apps/scripts/discover.sh https://github.com/user/repo
The discovery script will output:
- •Base image and OS information
- •Exposed ports
- •Environment variables
- •Volume mounts
- •Entrypoint and CMD
- •Package installations
- •Architecture support
2. Create Add-on Directory Structure
Use the scaffold template as a starting point:
# Copy scaffold to your add-on directory cp -r .claude/skills/ha-apps/scaffold/ /path/to/addons/myapp/ # Or for ingress-enabled add-on: cp -r .claude/skills/ha-apps/templates/ingress-nginx-s6-v3/ /path/to/addons/myapp/
3. Configure the Add-on
Edit the key configuration files based on discovery output:
config.yaml
name: "My Application"
version: "1.0.0"
slug: "my_application"
description: "My application wrapped for Home Assistant"
arch:
- amd64
- aarch64
# Enable ingress for embedded web UI
ingress: true
ingress_port: 8099
panel_icon: "mdi:application"
# Map discovered ports (if not using ingress)
# ports:
# 8080/tcp: 8080
# Map discovered volumes
map:
- type: addon_config
path: /config
- ssl
- share
# API access
homeassistant_api: true
hassio_api: true
# Options from discovery
options:
port: 8080
ssl: false
schema:
port: port
ssl: bool
Dockerfile
For ingress-enabled add-ons (using the ingress-nginx-s6-v3 template):
ARG BUILD_FROM
FROM ${BUILD_FROM}
# Install s6-overlay v3
ARG S6_OVERLAY_VERSION="3.2.2.0"
ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz /tmp
RUN tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz
ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-x86_64.tar.xz /tmp
RUN tar -C / -Jxpf /tmp/s6-overlay-x86_64.tar.xz
# Install nginx for ingress
RUN apk add --no-cache nginx
# Install your application
RUN apk add --no-cache \
python3 \
your-app-dependencies
# Copy rootfs (includes nginx config and s6 services)
COPY rootfs /
# Set up entrypoint
ENTRYPOINT ["/init"]
For basic add-ons (using the scaffold template):
ARG BUILD_FROM
FROM ${BUILD_FROM}
# Install your application and dependencies
RUN apk add --no-cache \
bash \
your-app-dependencies
# Copy rootfs structure
COPY rootfs /
# Standard entrypoint for HA add-ons
CMD ["/run.sh"]
Directory Structure
Ingress-Enabled Add-on (templates/ingress-nginx-s6-v3/)
myapp/
├── config.yaml # Add-on configuration
├── Dockerfile # Container build instructions
├── build.yaml # Multi-arch build config
├── DOCS.md # Add-on documentation
├── README.md # GitHub README
├── rootfs/
│ ├── etc/
│ │ ├── cont-init.d/ # Init scripts (run once at startup)
│ │ │ ├── 10-banner.sh # Show startup banner
│ │ │ ├── 20-nginx.sh # Configure nginx
│ │ │ └── 30-app.sh # Configure your app
│ │ ├── services.d/ # S6 service definitions
│ │ │ ├── app/
│ │ │ │ ├── run # Run your application
│ │ │ │ └── finish # Handle app exit
│ │ │ └── nginx/
│ │ │ ├── run # Run nginx
│ │ │ └── finish # Handle nginx exit
│ │ └── nginx/
│ │ ├── nginx.conf # Main nginx config
│ │ ├── servers/
│ │ │ └── ingress.conf # Ingress proxy config
│ │ └── includes/ # Reusable nginx configs
│ └── usr/local/bin/
│ └── myapp # Your application binary
└── translations/
└── en.yaml # UI translations
Basic Add-on (scaffold/)
myapp/
├── config.yaml # Add-on configuration
├── Dockerfile # Container build instructions
├── build.yaml # Multi-arch build config
├── DOCS.md # Add-on documentation
├── README.md # GitHub README
├── run.sh # Main startup script
├── rootfs/
│ ├── etc/
│ │ ├── cont-init.d/ # Init scripts
│ │ │ ├── 00-banner.sh
│ │ │ └── 01-setup.sh
│ │ └── services.d/ # S6 services (optional)
│ │ └── example-app/
│ │ ├── run
│ │ └── finish
│ └── usr/bin/
│ └── example-app # Your application
└── translations/
└── en.yaml
Key Components Explained
S6-Overlay v3 Process Supervision
S6-overlay provides proper init system for containers:
Init stages:
- •cont-init.d/ - One-time initialization scripts (numbered 00-99)
- •services.d/ - Long-running supervised services
- •cont-finish.d/ - Cleanup scripts on shutdown
Service structure (services.d/myapp/):
- •
run- Script to start your service (must run in foreground) - •
finish- Optional cleanup when service exits
Example service run script:
#!/command/execlineb -P with-contenv s6-setuidgid abc /usr/local/bin/myapp --config /config/myapp.conf
See @references/s6-overlay.md for detailed documentation.
Bashio Helper Functions
Bashio provides convenient bash functions for Home Assistant add-ons:
#!/usr/bin/env bashio
# Logging
bashio::log.info "Starting application..."
bashio::log.warning "Port already in use"
bashio::log.error "Failed to start"
# Read add-on options
PORT=$(bashio::config 'port')
SSL=$(bashio::config 'ssl')
# Check if option exists
if bashio::config.has_value 'ssl_cert'; then
CERT=$(bashio::config 'ssl_cert')
fi
# API calls
bashio::api.supervisor GET /info
bashio::services.publish "mqtt" \
"host=core-mosquitto" \
"port=1883"
See @references/bashio-reference.md for complete API reference.
Ingress Configuration
What is ingress? Ingress allows your add-on's web UI to be embedded directly in Home Assistant, accessible via the sidebar without exposing ports.
How it works:
- •Your app runs on internal port (e.g., 8080)
- •Nginx proxies from ingress port (8099) to your app
- •Home Assistant routes
/api/hassio_ingress/<addon>/to your nginx - •Users access via Home Assistant UI sidebar
Nginx ingress configuration (rootfs/etc/nginx/servers/ingress.conf):
server {
listen 8099 default_server;
location / {
allow 172.30.32.2;
deny all;
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Init script to configure nginx (rootfs/etc/cont-init.d/20-nginx.sh):
#!/usr/bin/env bashio
bashio::log.info "Configuring nginx..."
# Get ingress configuration
INGRESS_PORT=$(bashio::addon.ingress_port)
bashio::log.info "Ingress will be available on port ${INGRESS_PORT}"
# Update nginx config
sed -i "s/listen 8099/listen ${INGRESS_PORT}/g" /etc/nginx/servers/ingress.conf
Build Configuration
build.yaml - Multi-architecture builds:
build_from: amd64: "ghcr.io/home-assistant/amd64-base:3.19" aarch64: "ghcr.io/home-assistant/aarch64-base:3.19" args: S6_OVERLAY_VERSION: "3.2.2.0"
Home Assistant base images come with:
- •Proper init system
- •bashio pre-installed
- •Common utilities
- •Multi-arch support
Configuration Schema
Define user-configurable options in config.yaml:
options: port: 8080 log_level: info ssl: false certfile: fullchain.pem keyfile: privkey.pem schema: port: port # Port number (1-65535) log_level: list(trace|debug|info|warning|error) ssl: bool certfile: str? # Optional string keyfile: str?
Access in scripts:
PORT=$(bashio::config 'port') LOG_LEVEL=$(bashio::config 'log_level')
See @references/config-reference.md for all schema types.
Common Patterns
Pattern 1: Simple Web Application
For apps with a single web interface:
- •Use ingress template
- •Configure nginx to proxy to your app's port
- •Set
ingress: truein config.yaml - •Add init script to start your app
Pattern 2: Service with Optional Web UI
For apps that can run headless or with UI:
- •Use basic scaffold
- •Make ingress conditional:
# config.yaml options: enable_ui: true schema: enable_ui: bool
- •In init script:
if bashio::config.true 'enable_ui'; then
# Enable nginx
bashio::log.info "Enabling web UI"
fi
Pattern 3: Multi-Service Application
For apps with multiple components:
- •Create multiple service directories in
services.d/ - •Use dependencies in s6-rc format (see v3_example/)
- •Each service gets its own run/finish scripts
services.d/
├── database/
│ ├── run
│ └── finish
├── backend/
│ ├── run
│ └── finish
└── frontend/
├── run
└── finish
Discovery Script Details
The discovery script (scripts/discover.sh) analyzes:
For Docker images:
bash discover.sh linuxserver/plex:latest
- •Pulls image and inspects metadata
- •Extracts environment variables, volumes, ports
- •Analyzes Docker history for package installations
- •Detects base OS and architecture
For GitHub repositories:
bash discover.sh https://github.com/user/repo
- •Clones repository
- •Finds and analyzes Dockerfile
- •Extracts docker-compose configuration
- •Searches for documentation
Output includes:
- •Architecture support recommendations
- •Port mappings for config.yaml
- •Volume mapping suggestions
- •Base image information
- •Package dependencies to install
- •Environment variables to expose as options
Testing Your Add-on
Local Testing
- •Build the add-on:
docker build -t local/myapp .
- •Run with Home Assistant:
docker run --rm \ -v /path/to/config:/config \ -v /path/to/data:/data \ -p 8099:8099 \ local/myapp
- •Check logs:
docker logs -f <container-id>
Development in Home Assistant
- •
Add local add-on repository:
- •Go to Supervisor → Add-on Store → ⋮ → Repositories
- •Add your repository path
- •
Install and test:
- •Install add-on from local repository
- •Configure options
- •Start add-on
- •Check logs in Supervisor → Add-on → Log tab
Common Issues
Add-on won't start:
- •Check Dockerfile builds successfully
- •Verify ENTRYPOINT or CMD is correct
- •Check service run scripts are executable
- •Review logs for error messages
Ingress not working:
- •Verify
ingress: truein config.yaml - •Check nginx is listening on correct port
- •Ensure your app accepts connections from 127.0.0.1
- •Check nginx logs in add-on logs
Options not being read:
- •Verify schema matches options structure
- •Check bashio::config calls use correct keys
- •Ensure default values are valid
Advanced Topics
Custom S6 Services (v3 format)
The v3_example/ directory shows s6-rc service format:
s6-overlay/s6-rc.d/
├── myapp/ # Service definition
│ ├── type # "longrun" or "oneshot"
│ ├── run # Start script
│ ├── finish # Exit handler
│ └── dependencies.d/ # Service dependencies
│ └── base # Wait for base services
└── user/
└── contents.d/
└── myapp # Register service
Environment Variables
Expose configuration as environment variables:
#!/usr/bin/env bashio # Read options export APP_PORT=$(bashio::config 'port') export APP_LOG_LEVEL=$(bashio::config 'log_level') export APP_DATA_DIR="/data" # Start app with environment exec /usr/local/bin/myapp
Volume Permissions
Handle volume ownership in init script:
#!/usr/bin/env bashio # Ensure directories exist with correct permissions mkdir -p /data/app chown -R abc:abc /data/app # Or use bashio helpers bashio::fs.directory_exists "/config/app" || mkdir -p "/config/app"
Multi-Architecture Support
Build for multiple architectures:
# build.yaml build_from: amd64: "ghcr.io/home-assistant/amd64-base:3.19" aarch64: "ghcr.io/home-assistant/aarch64-base:3.19" armv7: "ghcr.io/home-assistant/armv7-base:3.19"
Use build arguments for arch-specific downloads:
ARG TARGETARCH
RUN case "${TARGETARCH}" in \
amd64) ARCH=x86_64 ;; \
aarch64) ARCH=aarch64 ;; \
armv7) ARCH=armv7 ;; \
esac && \
wget https://example.com/app-${ARCH}.tar.gz
Reference Documentation
This skill includes comprehensive reference documentation:
- •bashio-reference.md - Complete bashio API reference with examples
- •s6-overlay.md - Official s6-overlay v3 documentation
- •dockerfile-ref.md - Dockerfile instruction reference
- •config-reference.md - Complete config.yaml schema reference
Example: Creating a Plex Add-on
Let's walk through creating a Plex Media Server add-on:
1. Discover Plex Image
bash scripts/discover.sh linuxserver/plex:latest
Output shows:
- •Base: Ubuntu
- •Ports: 32400/tcp, 1900/udp, etc.
- •Volumes: /config, /movies, /tv
- •Environment: PUID, PGID, TZ
2. Create Add-on Structure
cp -r templates/ingress-nginx-s6-v3/ ~/addons/plex/ cd ~/addons/plex/
3. Configure config.yaml
name: "Plex Media Server"
version: "1.0.0"
slug: "plex"
description: "Plex Media Server for Home Assistant"
arch:
- amd64
- aarch64
ingress: true
ingress_port: 8099
panel_icon: "mdi:plex"
map:
- type: addon_config
path: /config
- type: media
path: /media
read_only: false
options:
claim_token: ""
schema:
claim_token: str?
4. Update Dockerfile
ARG BUILD_FROM
FROM ${BUILD_FROM}
# Install s6-overlay
ARG S6_OVERLAY_VERSION="3.2.2.0"
ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz /tmp
RUN tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz
ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-x86_64.tar.xz /tmp
RUN tar -C / -Jxpf /tmp/s6-overlay-x86_64.tar.xz
# Install nginx and dependencies
RUN apk add --no-cache nginx curl
# Install Plex
RUN curl -o /tmp/plexmediaserver.tar.gz \
https://downloads.plex.tv/plex-media-server-new/1.32.0.6100/linux/PlexMediaServer-1.32.0.6100-x86_64.tar.gz && \
tar -xzf /tmp/plexmediaserver.tar.gz -C /opt
COPY rootfs /
ENTRYPOINT ["/init"]
5. Configure Nginx (rootfs/etc/nginx/servers/ingress.conf)
server {
listen 8099;
location / {
allow 172.30.32.2;
deny all;
proxy_pass http://127.0.0.1:32400;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
}
}
6. Create Plex Service (rootfs/etc/services.d/plex/run)
#!/command/execlineb -P with-contenv s6-setuidgid abc /opt/plexmediaserver/Plex\ Media\ Server
7. Add Init Script (rootfs/etc/cont-init.d/30-plex.sh)
#!/usr/bin/env bashio
bashio::log.info "Configuring Plex Media Server..."
# Get claim token if provided
if bashio::config.has_value 'claim_token'; then
CLAIM_TOKEN=$(bashio::config 'claim_token')
bashio::log.info "Using claim token for Plex setup"
export PLEX_CLAIM="${CLAIM_TOKEN}"
fi
# Set Plex preferences
export PLEX_MEDIA_SERVER_APPLICATION_SUPPORT_DIR="/config"
8. Test
# Build docker build -t local/plex . # Run docker run --rm \ -v $(pwd)/test-config:/config \ -v $(pwd)/test-media:/media \ -p 8099:8099 \ local/plex
9. Access via Home Assistant
After installing in HA:
- •Add-on appears in sidebar with Plex icon
- •Click to access Plex web interface
- •All traffic proxied through Home Assistant ingress
- •No need to expose port 32400
Tips and Best Practices
- •Always use ingress for web UIs - Better user experience and security
- •Use bashio helpers - Cleaner code and better error handling
- •Follow s6 best practices - Proper process supervision and init
- •Test multi-arch - Build and test on both amd64 and aarch64
- •Document options - Clear descriptions in DOCS.md
- •Version properly - Semantic versioning (1.0.0, 1.1.0, 2.0.0)
- •Include translations - At minimum English in translations/en.yaml
- •Handle secrets safely - Use options schema with password type
- •Log appropriately - Use bashio logging for consistency
- •Clean up temp files - Remove downloads in Dockerfile
Resources
- •Home Assistant Add-on Documentation
- •S6-Overlay Documentation
- •Bashio GitHub
- •Home Assistant Base Images
- •Add-on Example Repository
Troubleshooting
Add-on won't install
- •Check config.yaml syntax is valid YAML
- •Verify all required fields are present
- •Check image exists and is accessible
Ingress shows 502 error
- •Ensure your app is listening on 127.0.0.1 or 0.0.0.0
- •Check nginx is running:
ps aux | grep nginx - •Verify proxy_pass port matches your app's port
Options not applying
- •Check schema matches options structure exactly
- •Use
bashio::config 'key'not environment variables - •Verify default values in options are valid
Service won't start
- •Make run script executable:
chmod +x run - •Check script has proper shebang
- •Ensure service runs in foreground (no & or daemon mode)
- •Check dependencies in dependencies.d/ exist
Logs show permission errors
- •Use s6-setuidgid in service run scripts
- •Set correct ownership in init scripts
- •Check volume permissions on host
Next Steps
After creating your add-on:
- •Test thoroughly - Install in Home Assistant and test all features
- •Write documentation - Complete DOCS.md with all options and usage
- •Create repository - Set up GitHub repository for add-on
- •Configure CI/CD - Use GitHub Actions for automated builds
- •Publish - Share with community via add-on repository
- •Maintain - Keep dependencies updated and respond to issues
Summary
This skill provides everything needed to create professional Home Assistant add-ons from Docker images:
- •Discovery script to analyze existing images
- •Scaffold templates for quick setup
- •Ingress template for embedded web UIs
- •Reference docs for all HA add-on technologies
- •Examples and patterns for common scenarios
Use the discovery script to understand your target application, choose the appropriate template (basic or ingress), customize the configuration, and test thoroughly before publishing.