ESPHome docker container that supports non-root operation.
Licensed under the MIT License
Code and Pipeline is on GitHub.
Docker image is published on Docker Hub.
Image is rebuilt weekly, or when a new ESPHome version is released, picking up the latest ESPHome release and upstream container updates.
- Version 1.6:
- Support
tmpfsfor optional/tmpvolume, use/tmpinstead of/cache/tmpfor temp files. - Make
/cachevolume mount optional. - Replace
locales-allpackage withlocalesto reduce image size.
- Support
- Version 1.5:
- Using Python 3.13 base image.
- Version 1.4:
- Removed custom handling for
ESPHOME_VERBOSEenabling--verbose, PR merged.
- Removed custom handling for
- Version 1.3:
- Added Dev Container Workspace that maps
configandcachevolumes.
- Added Dev Container Workspace that maps
- Version 1.2:
- Delete temp directory contents and prune PIO cached content on startup.
- Added Dev Container that can be used for ESPHome or PlatformIO debugging.
- Version 1.1:
- Added daily actions job to trigger a build if the ESPHome version changed.
- Version 1.0:
- Initial public release.
volumes:/config: Volume mapping to project files./cache(Optional) : Volume mapping to runtime generated content./tmp(Optional) : Volume mapping for temp files.
user(Optional) : Run the container under the specified user account.- Use the
uid:gidnotation, e.g.user: 1001:100.- Get the
uid:sudo id -u nonroot. - Get the
gid:sudo id -g nonroot.
- Get the
- Use an existing user or create a system account on the host.
adduser --no-create-home --disabled-password --system --group users nonroot.
- Omitting the
useroption will run under defaultrootaccount. - Make sure the container user has permissions to the mapped
/configand/cachevolumes.sudo chown -R nonroot:users /data/esphomesudo chmod -R ug=rwx,o=rx /data/esphome
- Use the
environment:ESPHOME_VERBOSE(Optional) : Enables verbose log output, e.g.ESPHOME_VERBOSE=true.ESPHOME_DASHBOARD_USE_PING(Optional) : Usepinginstead ofmDNSto test if nodes are up, e.g.ESPHOME_DASHBOARD_USE_PING=true.TZ(Optional) : Sets the timezone, e.g.TZ=America/Los_Angeles, default isEtc/UTC.
services:
esphome:
image: docker.io/ptr727/esphome-nonroot:latest
container_name: esphome-test
restart: unless-stopped
user: 1001:100
environment:
- TZ=America/Los_Angeles
- ESPHOME_VERBOSE=true
- ESPHOME_DASHBOARD_USE_PING=true
network_mode: bridge
ports:
- 6052:6052
volumes:
- /data/esphome/config:/config
- /data/esphome/cache:/cache
tmpfs:
- /tmp:size=1g,mode=1777# Create nonroot user
adduser --no-create-home --disabled-password --system --group users nonroot
id nonroot
# uid=1001(nonroot) gid=100(users) groups=100(users)
# Prepare directories for use by nonroot:users
mkdir -p /data/esphome/config /data/esphome/cache
sudo chown -R nonroot:users /data/esphome
sudo chmod -R ug=rwx,o=rx /data/esphome
# Launch stack
docker compose --file ./Docker/Compose.yml up --detach
# Open browser: http://localhost:6052
# Attach shell: docker exec -it --user 1001:100 esphome-test /bin/bash
# Destroy stack
docker compose --file ./Docker/Compose.yml down --volumes# esphome version
docker run --rm --pull always --name esphome-test -v /data/esphome/config:/config -v /data/esphome/cache:/cache ptr727/esphome-nonroot:latest esphome versionlatest: Pulling from ptr727/esphome-nonroot
Digest: sha256:8f32848551446d0420390477fccb8c833d879b640b95533f443cb623882e9688
Status: Image is up to date for ptr727/esphome-nonroot:latest
Version: 2024.5.5# /bin/bash
docker run --rm --user 1001:100 -it --pull always --name esphome-test -v /data/esphome/config:/config -v /data/esphome/cache:/cache ptr727/esphome-nonroot:latest /bin/bashlatest: Pulling from ptr727/esphome-nonroot
Digest: sha256:8f32848551446d0420390477fccb8c833d879b640b95533f443cb623882e9688
Status: Image is up to date for ptr727/esphome-nonroot:latest
I have no name!@012d4b62d376:/config$ id
uid=1001 gid=100(users) groups=100(users)
I have no name!@012d4b62d376:/config$ esphome:
image: docker.io/ptr727/esphome-nonroot:latest
container_name: esphome
hostname: esphome
domainname: ${DOMAIN_NAME}
restart: unless-stopped
user: ${USER_NONROOT_ID}:${USERS_GROUP_ID}
group_add:
- ${DOCKER_GROUP_ID}
security_opt: # Use with care
- seccomp=unconfined
- apparmor=unconfined
environment:
- TZ=${TZ}
# - ESPHOME_VERBOSE=true
volumes:
- ${APPDATA_DIR}/esphome/config:/config
- ${APPDATA_DIR}/esphome/cache:/cache
tmpfs:
- /tmp:size=1g,mode=1777
networks:
public_network:
ipv4_address: ${ESPHOME_IP}
mac_address: ${ESPHOME_MAC}
local_network:
stack_network:
labels:
- traefik.enable=true
- traefik.http.routers.esphome.rule=HostRegexp(`^esphome${DOMAIN_REGEX}$$`)
- traefik.http.services.esphome.loadbalancer.server.scheme=http
- traefik.http.services.esphome.loadbalancer.server.port=6052- Running containers as non-privileged and as non-root is a docker best practice.
- The official ESPHome docker container does not support running as a non-root user.
- Issue analysis based on ESPHome
2024.5.5(current version as of writing)Dockerfile:PLATFORMIO_GLOBALLIB_DIR=/piolibssets the PlatformIOgloballib_diroption to/piolibs./piolibsis not mapped to an external volume./piolibshas default permissions and requiresrootwrite permissions at runtime.- The
globallib_diroption has been deprecated.This option is DEPRECATED. We do not recommend using global libraries for new projects. Please use a declarative approach for the safety-critical embedded development and declare project dependencies using the lib_deps option.
platformio_install_deps.pyinstalls global libraries usingpio pkg install -g, the-goption has been deprecated.We DO NOT recommend installing libraries in the global storage. Please use the lib_deps option and declare library dependencies per project.
- The presence of a
/cachedirectory changespio_cache_baseto/cache/platformio, the default is/config/.esphome/platformioPLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms",PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages", andPLATFORMIO_CACHE_DIR="${pio_cache_base}/cache"is explicitly set as child directories ofpio_cache_base.- It is simpler to set
PLATFORMIO_CORE_DIRPlatformIOcore_diroption, and not settingPLATFORMIO_PLATFORMS_DIRplatforms_dir,PLATFORMIO_PACKAGES_DIRpackages_dir, andPLATFORMIO_CACHE_DIRcache_diroptions, that are by default child directories ofcore_dir.
- The presence of a
/builddirectory sets theESPHOME_BUILD_PATHenvironment variable, that sets theCONF_BUILD_PATHESPHomebuild_pathoption, the default is/config/.esphome/build.- The directory presence detection could override an explicitly set
ESPHOME_BUILD_PATHoption.
- The directory presence detection could override an explicitly set
ESPHOME_DATA_DIRcan be used to set the ESPHomedata_dirintermediate build output directory, the default is/config/.esphome, or hardcoded to/datafor the HA addon image.PLATFORMIO_CORE_DIRPlatformIOcore_diroption is not set and defaults to~/.platformio.PIP_CACHE_DIRis not set and defaults to~/.cache/pip.HOME(~) is not set and defaults to e.g./home/[username]or/or/nonexistentthat either does not exists or the executing user does not have write permissions.
- Use Python docker base image simplifying use for Python in a container environment.
- Use a multi-stage build minimizing size and layer complexity of the final stage.
- Build
wheelarchives for the platform in the builder stage, and install the platform specific generated wheel packages in the final stage. - Set appropriate PlatformIO and ESPHome environment variables to store projects in
/configand dynamic and temporary content in/cachevolumes. - Refer to
Dockerfilefor container details. - Refer to
BuildDockerPush.ymland for pipeline details.
The included Dev Container can be used for ESPHome Python or PlatformIO C++ debugging in VSCode.
Detailed debug setup details are beyond the scope of this project, refer to my ESPHome-Config project for slightly more complete debugging setup instructions.