A daemon that automatically updates /etc/hosts with Docker container hostnames from specified Docker networks.
- Authoritative Host Management: Reconciles complete Docker snapshots into
/etc/hostsafter network events and periodic validation - Multi-Network Support: Monitors multiple Docker networks in one atomic update, with collision-safe qualified aliases
- Debounced Event Handling: Prevents excessive updates during container churn
- Periodic Validation: Ensures
/etc/hostsstays in sync with Docker networks - Current-State Health: Reports active failures and retains bounded diagnostic history without time-based false recovery
- Validated Recovery: Recovers corrupt managed markers only from a valid backup and otherwise fails without overwriting the target
- Transactional Updates: Uses adjacent temporary files, durable atomic replacement, readback validation, and bounded rollback
- Single-Writer Safety: Rejects overlapping daemon or regeneration processes for the same hosts file and detects concurrent destination changes before replacement
- Configurable Section Management: Manages a clearly marked section in
/etc/hostswhile preserving other entries
- Linux system with Docker installed
- Root access (to modify
/etc/hosts) - Go 1.27+ (for building from source)
# Clone the repository
git clone https://github.com/saltyorg/sdhm.git
cd sdhm
# Build the binary
make build
# Install to /usr/local/bin (requires root)
sudo make installDownload the latest release from the releases page, then:
# Download and install
curl -s https://api.github.com/repos/saltyorg/sdhm/releases/latest | jq -r '.assets[] | select(.name == "sdhm_linux_amd64") | .browser_download_url' | xargs sudo curl -Lo /usr/local/bin/sdhm && sudo chmod +x /usr/local/bin/sdhmRun with default settings (monitors the saltbox network):
sudo sdhmMonitor a specific Docker network:
sudo sdhm --networks mynetworkMonitor multiple Docker networks:
sudo sdhm --networks "bridge,mynetwork,webproxy"Choose which monitored network also receives bare aliases:
sudo sdhm --networks "saltbox,backend" --default-network backendRun with custom validation interval:
sudo sdhm --interval 10mEnable health check on all interfaces (useful for Docker health checks):
sudo sdhm --health-addr 0.0.0.0 --health-port 8080Testing with a custom hosts file (useful for development):
sdhm --hosts-file /tmp/test-hosts --networks bridge| Flag | Short | Default | Description |
|---|---|---|---|
--networks |
-n |
saltbox |
Comma-separated list of Docker networks to monitor |
--default-network |
Automatic | Monitored network that also receives bare host aliases | |
--interval |
-i |
5m |
Periodic validation interval (e.g., 30s, 5m, 1h, 1d) |
--health-port |
-p |
8080 |
Health check HTTP server port |
--health-addr |
127.0.0.1 |
IP address to bind health check server | |
--hosts-file |
/etc/hosts |
Path to hosts file (useful for testing) | |
--backup-file |
/etc/hosts.backup |
Path to backup file | |
--section-name |
DOCKER CONTAINERS |
Name for managed section in hosts file | |
--debounce-delay |
1s |
Debounce delay for event handling | |
--debounce-max-delay |
5s |
Maximum debounce delay |
--health-addr must be a literal IPv4 or IPv6 address, and --health-port must be in 1..65535. The health endpoint is registered as GET /health; unsupported methods receive HTTP 405.
--networks/-n keeps its historical comma-separated syntax. SDHM trims surrounding spaces, ignores empty items, removes duplicates while preserving first occurrence order, and requires at least one network.
The resolved default network receives both alias and alias.<network> names. Every secondary network receives only alias.<network>, preventing bare-name collisions. --default-network must name a monitored network. When it is omitted, saltbox is preferred if present; otherwise the first configured network becomes the default. Existing startup commands using only --networks, --interval, and --health-port therefore require no new flag.
Interval and debounce values accept positive, case-sensitive Go durations, including milliseconds and compound values such as 500ms, 1m30s, and 2h15m. Exact positive integer days such as 1d or 7d are also accepted. Zero and negative durations are rejected.
SDHM provides a health check HTTP endpoint for monitoring:
# Check health status
curl http://127.0.0.1:8080/healthResponse:
{
"error_count": 0,
"errors": [],
"healthy": true,
"message": "No errors recorded",
"status": "ok"
}From the moment the listener starts, the endpoint returns HTTP 503 with message: "System initializing" until hosts preparation and the first reconciliation attempt have completed. This readiness gate does not add an error record. After initialization, the endpoint returns HTTP 200 only when no concern is currently active and HTTP 503 while Docker snapshots, event streaming, hosts updates, or recovery remain failed. A failed first reconciliation therefore stays at 503 under its actual Docker or hosts concern. A concern clears only after that same operation succeeds; elapsed time alone never marks it recovered. Up to ten historical records remain available for diagnosis, so error_count is retained history rather than the number of active concerns. Active records use unknown recovery fields, while superseded or recovered records use 0s.
Create /etc/systemd/system/sdhm.service:
[Unit]
Description=Saltbox Docker Hosts Manager
After=docker.service
BindsTo=docker.service
[Service]
Type=simple
ExecStart=/usr/local/bin/sdhm --networks saltbox --interval 5m
Restart=always
RestartSec=10
[Install]
WantedBy=docker.serviceThis binding stops SDHM during Docker teardown before authoritative transitional snapshots can rewrite the last converged hosts state.
Enable and start the service:
sudo systemctl daemon-reload
sudo systemctl enable sdhm
sudo systemctl start sdhmCheck service status:
sudo systemctl status sdhm
sudo journalctl -u sdhm -fHealth answers the current question: whether SDHM is ready and whether an active concern makes it unhealthy. The journal answers the historical question: when the daemon crossed a lifecycle, failure, recovery, or automatic-repair boundary. A healthy response does not erase the corresponding warning from the journal.
For the complete Saltbox service history, use:
sudo journalctl -u saltbox_managed_docker_update_hosts.serviceSDHM writes native journal priorities when systemd provides JOURNAL_STREAM, so warning-or-higher transitions can be filtered without parsing MESSAGE:
sudo journalctl -p warning -u saltbox_managed_docker_update_hosts.serviceThe implemented daemon transition records are:
| Transition | Level / expected journal priority | Message and fields | Meaning |
|---|---|---|---|
| Process invocation | INFO / 6 | starting SDHM; version, networks, default_network, interval, health_addr |
SDHM has been invoked. health_addr is the configured bound address and port. |
| Initial reconciliation succeeds | INFO / 6 | SDHM ready |
The listener is bound, preparation completed, and the first authoritative reconciliation succeeded. Health may now return 200 when no current concern is active. |
| Initial reconciliation fails | WARN / 4 | reconciliation failed; phase=initial, err, retry_in |
Initialization finished but the first reconciliation failed. Health remains 503 until its Docker or hosts concern succeeds. |
| Later reconciliation fails | WARN / 4 | reconciliation failed; err, retry_in |
A previously running reconciliation failed and will retry with bounded backoff. |
| Reconciliation recovers | INFO / 6 | reconciliation recovered |
A reconciliation succeeded after a logged reconciliation failure. |
| Docker event stream fails | WARN / 4 | Docker event stream unavailable; err, retry_in |
Event observation is unavailable and will reconnect with bounded backoff. |
| Docker event stream recovers | INFO / 6 | Docker event stream recovered; evidence=event or evidence=stable |
A previously failed stream delivered an event, or stayed connected for its stability interval. |
| Marker repair succeeds | WARN / 4 | hosts file recovered from validated backup |
Preparation restored corrupt target markers from a validated backup. |
| Late replacement rollback succeeds | WARN / 4 | reconciliation failed; err includes target restored from retained snapshot |
A replacement failed after changing the target, but SDHM restored the retained snapshot. |
| Clean daemon completion | INFO / 6 | SDHM stopped |
Run completed cleanly after shutdown cleanup; it is not emitted for a terminal daemon error. |
| Terminal command error | ERROR / 3 | One concise error string, no fields | The command is exiting unsuccessfully; invalid configuration does not add a Cobra usage dump. |
The sdhm regenerate command additionally records regenerating hosts file at INFO with its path, and either command warns SDHM should run as root to modify the hosts file when it is not running as root.
SDHM intentionally does not log individual Docker events, container names, unmonitored networks, successful periodic no-op reconciliations, ordinary successful hosts mutations, repeated reconnect failures with the same error, abandoned health-response writes, or intentionally unpublishable endpoint omissions. This keeps the journal a bounded transition history instead of an update-by-update event feed.
Live systemd journal-priority acceptance passed on 2026-09-02 for reviewed code head 1e725d1283a05c8776415ce8422b8025f8a79cc5 and its static linux/amd64 artifact (SHA-256 3a2dcbd7ada7c9d07751be6609af3af9e42c81e5b4e0022d5c100a3599dbb76a) on an Ubuntu 26.04 minimal VM using isolated synthetic networks and temporary hosts files. This qualification does not cover a real Saltbox service deployment, a core-profile run, a release artifact, or the final documentation-head artifact (340aaf06a2901271709d366f99333c6a8c14b9fd; local build SHA-256 628fadc3a765e169f14903577282b836533368f3d46b602729e7c9f825e80617), which was not VM-run.
- Startup: SDHM pings Docker, binds the health listener in an initializing 503 state, validates or prepares the managed hosts section, and attempts one authoritative reconciliation before reporting readiness or starting background work
- Discovery: One authoritative list/inspect pass gathers every publishable endpoint on the configured networks. Container-list or non-not-found inspect failures preserve the current hosts file, while endpoints without usable settings, an IP address, or aliases are omitted because they have no valid hosts mapping. In the verified crash-loop case, Docker kept the restarting container listed while clearing its IP, so omission prevents it from blocking healthy updates
- Scheduling: Network events are debounced, periodic validation requests immediate reconciliation, and failed work retries with bounded backoff
- Hosts File Update: The complete publishable snapshot replaces one managed section while preserving every byte outside it:
# BEGIN DOCKER CONTAINERS 172.18.0.2 mycontainer 172.18.0.3 webserver # END DOCKER CONTAINERS - Backup & Recovery: Successful preparation, reconciliation, and regeneration leave the backup as a byte-identical current recovery mirror. Corrupt markers are restored only from a backup with one valid ordered marker pair; a missing or invalid backup fails closed without replacing the target. Symlink, FIFO, directory, and other non-regular target or backup paths are rejected rather than replaced.
sdhm regenerateremains available for an explicit fresh baseline.
make build# Run the complete local gate used by CI
make check
# Run tests with coverage
make test-coverage
# Run tests without the race detector
make test
# Run quick tests only
make test-short# Format code
make fmt
# Check formatting without modifying files
make fmt-check
# Check module files without modifying them
make tidy-check
# Run race-enabled tests
make test-race
# Run formatting, module, static analysis, vulnerability, race, vet, and build gates
make checkDependency maintenance is explicit and mutating:
# Update dependencies, then tidy the module
make update
# Tidy without updating dependency versions
make tidyRun make help to see all available targets:
all Run tests and build the binary
build Build the sdhm binary
clean Clean build artifacts and test files
test Run all tests (doesn't touch production /etc/hosts)
test-short Run short tests
test-coverage Run tests with coverage report
deps Download dependencies
update Update dependencies to latest versions
tidy Tidy go.mod
fmt Format code
fmt-check Check that tracked Go files are formatted
vet Run go vet
tidy-check Check module files without modifying them
staticcheck Run pinned static analysis
vuln-check Check reachable dependencies against the Go vulnerability database
test-race Run all tests with the race detector
check Run formatting, module, static analysis, vulnerability, race, vet, and build gates
run Run the application with example interval
install Install the binary to /usr/local/bin
uninstall Remove the binary from /usr/local/bin
help Show this help message
SDHM requires root access to modify /etc/hosts:
# Run with sudo
sudo sdhm-
Verify the correct network is being monitored:
docker network ls sudo sdhm --networks your-network-name
-
Check Docker events are being received:
docker events --filter type=network
-
Verify containers are connected to the monitored network:
docker network inspect your-network-name
If accessing the health check from another container:
# Allow binding to all interfaces
sudo sdhm --health-addr 0.0.0.0Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes
- Run the complete gate:
make check - Submit a pull request
This project is licensed under the GNU General Public License v3.0 - see the LICENSE file for details.
- Issues: GitHub Issues
Built for the Saltbox project.