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.