esphome-deployment
A CLI Tool for managing compilation and deployment of ESPHome Device configurations.
Features
- 🚀 Batch Deployment: Deploy to multiple devices with a single command.
- 📦 Smart Caching: Avoids redundant compiles by tracking configuration hashes.
- 🕵️ Change Detection: Only uploads binaries if the resulting build actually changed.
- 🔄 VCS State Tracking: Store deployment metadata in Git to sync state across CI/CD or multiple machines.
- 📋 Detailed Logging: Automatic per-device log capture for easy debugging.
Setup
Prerequisites
- ESPHome CLI must be installed in your
PATH. - A Git repository for your ESPHome device configurations.
Reproducible Builds
For the caching mechanism of esphome-deployment to work across machines, the firmware images produced by ESPHome need to be made reproducible. This means that given the same input, the build process should always produce the exact same output binary, bit for bit. Unfortunately, ESPHome does not currently guarantee reproducible builds out of the box, but there are a couple of options we can add to our configuration YAML to remedy this:
esphome: Section
packages/esphome.yaml
esphome:
platformio_options:
build_flags:
# 1. Prevent warnings for redefining built-in macros
- -Wno-builtin-macro-redefined
# 2. Force a static date and time
- '-D__DATE__="\"Dec 29 2025\""'
- '-D__TIME__="\"23:00:00\""'
See: https://esphome.io/components/esphome/
esp32: Section
packages/chip/esp32-esp-idf.yaml
esp32:
framework:
type: esp-idf
sdkconfig_options:
# options to ensure reproducible builds (exact binary match)
CONFIG_APP_REPRODUCIBLE_BUILD: y
CONFIG_APP_COMPILE_TIME_DATE: n
CONFIG_APP_EXCLUDE_PROJECT_VER_VAR: y
CONFIG_APP_EXCLUDE_PROJECT_NAME_VAR: y
See: https://esphome.io/components/esp32/
Docker
If you prefer to use Docker, you can use the published image ghcr.io/markusressel/esphome-deployment from ghcr. Just ensure you mount your ESPHome configurations path
into it, and run the CLI commands as shown in the usage section below.
docker run --rm -it \
-v ~/esphome-configs:/config \
-u $(id -u):$(id -g) \
-e ESPHOME_DEPLOYMENT_DEPLOYMENT_COORDINATOR_MAX_WORKERS=1 \
ghcr.io/markusressel/esphome-deployment:latest \
esphome-deployment --help
Usage
Usage: esphome-deployment [OPTIONS] COMMAND [ARGS]...
Options:
--version Show the version and exit.
-h, --help Show this message and exit.
Commands:
clean Clean
compile Compile the given deployment(s)
config Print the current configuration
deploy Deploy (compile + upload) the given deployment(s)
upload Upload the given deployment(s)
Configuration
Main configuration
The main configuration for esphome-deployment can be specified in a file named .esphome_deployment.yaml located next to your ESPHome configurations (within your working
directory).
See .esphome_deployment.yaml for all available options.
Per-Device configuration
Each ESPHome configuration can optionally contain a .esphome_deployment: section (note the leading dot) to adjust the deployment behavior for that specific device.
Example:
.esphome_deployment:
deploy: true
tags:
- bluetooth_proxy
packages:
esphome: !include packages/esphome.yaml
chip: !include packages/chip/esp32-esp-idf.yaml
... rest of your esp home config ...
Repository layout, state files and logs
Recommended layout for a repository using esphome-deployment to deploy multiple ESPHome configurations:
<repo-root>/
├── packages/ # reusable package files (recommended)
│ ├── esphome.yaml
│ └── chip/...
├── .deployment-state/ # (generated) per-deployment JSON state files, commit this to VCS to share state across machines
├── .deployment-logs/ # (generated) CLI logs for compile/upload runs
├── .esphome_deployment.yaml # global config for esphome-deployment
├── secrets.yaml
├── your-device-1.yaml # individual deployment yamls (put in repo root)
└── your-device-2.yaml
Add this to your .gitignore:
# Ignore generated log files from esphome-deployment, but keep the deployment state files for VCS tracking
.deployment-logs/
Deployment State
esphome-deployment remembers the compilation and upload state for each deployment configuration in a JSON file within .deployment-state/.
- What they are: For every esphome invocation the CLI writes a log into
.deployment-logs/in the same directory as your device YAMLs. Filenames are like<deployment>_<command>_YYYYMMDD_HHMMSS.logand contain merged stdout/stderr from the esphome CLI. These logs are invaluable for debugging compile/upload issues.
Logs
esphome-deployment captures the output of each esphome CLI invocation and saves it to a log file in .deployment-logs/ for later inspection. This includes both stdout and stderr,
merged together with timestamps for easier debugging.
# list recent logs
ls -lt .deployment-logs/
# view a specific log
less .deployment-logs/<filename>.log
# follow a log in real time
tail -f .deployment-logs/<filename>.log
Contributing
Contributions to esphome-deployment are very welcome! If you have an idea for a new feature, found a bug or want to help out with documentation, please open an issue or submit a pull request.
Local Development
### Installation
1. Clone this repository as a git submodule next to your ESPHome configurations
* e.g. `git submodule add https://github.com/markusressel/esphome-deployment`
2. Ensure your configurations utilize the reproducible build options as shown above
* e.g. via packages for both ESPHome and ESP32 devices containing the options above
3. Use `poetry` (or any other method of your choice) to run esphome-deployment (see below)
```bash
python3 -m venv venv
. venv/bin/activate && pip install --upgrade pip poetry
poetry install -P ./esphome-deployment
License
esphome-deployment is licensed under the AGPLv3 License. By using or contributing to this project, you agree to comply with the terms of this license. Please review the LICENSE file for more details.
Metadata
Release files for esphome-deployment 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| esphome_deployment-0.1.0.tar.gz | 33.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| esphome_deployment-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 69.6 kB
Release files / esphome_deployment-0.1.0.tar.gz
| Download URL | esphome_deployment-0.1.0.tar.gz |
|---|---|
| Size | 33.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3e7e350700e06b7ee791322773fbf08c17e841b5ed74fe95b97db32f76537b76
|
|
BLAKE2b-256 checksum How to use checksums |
cfd11b2764a552b040a881d66d61267fad915f023c37ac7aa4e58fd5db5925b8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 25, 2026.
Transparency logRelease files / esphome_deployment-0.1.0-py3-none-any.whl
| Download URL | esphome_deployment-0.1.0-py3-none-any.whl |
|---|---|
| Size | 36.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
00090d8bcecca8060e8f84e0901a1de7c853d32c0f958cdffbcb934643a9dbc7
|
|
BLAKE2b-256 checksum How to use checksums |
7c8d704b0bf47bdfd902b434268f0a9cf13787c8eb0bfc5799626b6f1ddd0763
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 25, 2026.
Transparency log