Plan: Networking Hardening
Status
State: Active Started: 2026-05-14
Context
All 14 containers on sepia communicate over the Docker default bridge network. This provides no network isolation between services. A container compromise in any service (e.g. a web app RCE) can reach every other container.
Additionally, two services use network_mode: host:
- homeassistant — needs host network for mDNS, UPnP, and hardware access (Zigbee sticks)
- esphome — needs host network for mDNS discovery of ESP devices
No services use custom networks. All inter-service communication (e.g., dsmr → dsmrdb, grafana → influxdb) happens over the default bridge using container names.
Shuttle has the same plan as active/networking-hardening.md and has a full implementation.
Goals
- [ ] Design network topology with functional isolation
- [ ] Implement custom networks in compose files
- [ ] Assign services to appropriate networks
- [ ] Caddy gets access to all networks it needs to proxy
- [ ] Document
network_mode: hostexceptions and risks
Steps
Step 1: Design Network Topology
Define networks by function — services only connect to networks they need:
networks:
frontend: # Reverse proxy accessible (Caddy + proxied services)
backend: # Internal APIs, no direct external access
storage: # Databases
monitoring: # Metrics collection
sensors: # ESP devices
Proposed mapping:
| Network | Services | Purpose |
|---|---|---|
frontend |
caddy, grafana, homeassistant, dsmr, seafile-server, docs | Caddy needs to proxy to these |
backend |
dsmr, dsmrdb, influxdb, timescaledb, seafile-server, seafile-mysql, seafile-redis, grafana | Inter-service API calls |
storage |
dsmrdb, influxdb, timescaledb, seafile-mysql, seafile-redis | Database-only network |
monitoring |
collectd, influxdb, grafana | Metrics pipeline |
sensors |
esphome | ESP device communication |
Note: Services can belong to multiple networks (e.g., grafana is on frontend, backend, storage, and monitoring).
Step 2: Define Networks in compose.yaml
Add to the main /opt/compose.yaml:
networks:
frontend:
driver: bridge
backend:
driver: bridge
storage:
driver: bridge
monitoring:
driver: bridge
sensors:
driver: bridge
Step 3: Assign Services to Networks
Update each compose.<service>.yaml:
Example — grafana:
services:
grafana:
networks:
- frontend # Caddy proxies to it
- backend # DSMR data source queries
- storage # InfluxDB/TimescaleDB queries
- monitoring # Collectd metrics
Example — dsmr:
services:
dsmr:
networks:
- frontend # Caddy proxies to it
- backend # API access
- storage # Database access
dsmrdb:
networks:
- storage # Only accessible from backend services
Step 4: Handle network_mode: host Services
homeassistant and esphome use network_mode: host. This can't easily be replaced because:
- Home Assistant needs host networking for mDNS, UPnP discovery, and potential USB Zigbee/Bluetooth dongles
- ESPHome needs host networking for mDNS ESP device discovery
Document the risk: - These services have full host network access (no isolation) - If compromised, attacker has full LAN access from the host - Mitigation: keep them updated, minimize exposed ports, use Caddy auth
Consider macvlan/ipvlan as future improvement:
services:
homeassistant:
networks:
ha_net:
ipv4_address: 192.168.2.50 # Dedicated IP on LAN
networks:
ha_net:
driver: macvlan
driver_opts:
parent: enp2s0
ipam:
config:
- subnet: "192.168.2.0/24"
gateway: "192.168.2.1"
This would give homeassistant its own LAN IP without host networking. However, macvlan has limitations (no host-to-container communication without a second interface). Defer to a future plan.
Step 5: Update DNS Resolution
With custom networks, Docker's embedded DNS resolves container names automatically within each network. Services on different networks can't reach each other by name — this is the intended isolation.
Cross-network communication goes through Caddy (the reverse proxy), which is attached to all networks it needs to proxy to.
- Verification:
docker compose configvalidates network assignments - Verification: Grafana can still query InfluxDB and TimescaleDB
- Verification: Caddy can proxy to all frontend services
- Verification: Containers on different networks cannot ping each other
Rollback
Remove network assignments and the networks: top-level block from compose files. git checkout -- compose.*.yaml reverts.
Related
- REFERENCE/network.md (update with network topology)
- REFERENCE/services.md (update with network assignments)
- PLANS/active/compose-best-practices.md
- PLANS/active/dns-ad-blocker-migration.md (new DNS resolver may affect networking)
Created: 2026-05-14