Troubleshoot a Deployment¶
Diagnose and fix common problems with containerised cosalette applications.
Troubleshooting¶
- Container starts but no MQTT connection
- The broker hostname must be the Compose service name (e.g.,
mosquitto), notlocalhost. Inside a container,localhostrefers to the container itself. Verify name resolution withdocker exec myapp getent hosts mosquitto. - MQTT reconnect loop with
Connection reset by peeror anSSLerror -
The client may be attempting TLS against a broker that has no TLS listener.
MYAPP_MQTT__TLSdefaults totruesince 0.7.0 (ADR-062), so an app upgraded across that boundary can start a TLS handshake the broker cannot answer. This may cause the broker to logClient <unknown> disconnected due to protocol error, while the client retries with a growing backoff — which looks like a flaky broker rather than a misconfiguration. Certificate, protocol-version, and cipher errors can cause similar symptoms, so confirm the broker listener and its TLS configuration before disabling TLS.cosalette names this case once per run, before the first successful connection:
TLS handshake with mqtt.example:1883 failed ([SSL: UNEXPECTED_EOF_WHILE_READING] ...) — is the broker listening in plaintext? MQTT TLS is enabled by default (ADR-062); set MQTT__TLS=false if this broker has no TLS listener.Set
MYAPP_MQTT__TLS=falsefor a plaintext broker, or point the app at the broker's TLS listener (usually port 8883) and setMYAPP_MQTT__TLS_CA_FILE. The hint is advisory — it never stops the app, and reconnection is unaffected. - Permission denied on
/dev/ttyUSB0 -
The container needs access to the host device. Options:
- Add
device_cgroup_rules: ['c 188:* rmw']under the service — scopes access to a single device major/minor (preferred). - Map the specific device:
devices: ['/dev/ttyUSB0:/dev/ttyUSB0']. - Add the container user to the
dialoutgroup (group_add: [dialout]).
Do not reach for
privileged: trueto fix a device-permission error: it grants the container full access to every host device and is a trivial container-escape path on a Pi with GPIO/i²c/serial passthrough. The scoped options above are sufficient; never useprivileged: truein production. - Add
- Out of memory on Pi Zero 2 W
-
The Pi Zero 2 W has only 512 MB RAM. To reduce memory usage:
- Set
MYAPP_LOGGING__LEVEL=WARNINGto reduce log buffer pressure. - Use
MemoryStoreorNullStoreinstead ofSqliteStore. - Run
docker system pruneto reclaim space from old images. - Consider adding a swap file on the host.
- Set
- Container restarts in a loop
-
Check the exit code with
docker inspect --format='{{.State.ExitCode}}' myapp:Exit Code Meaning Action 1Configuration error Check env vars — missing required field, invalid value 3Runtime error Check logs with docker logs myappfor the root cause4Task failure (ADR-081) A device, telemetry, periodic or stream task kept failing. Search the logs for the CRITICALline fromcosalette._supervisor— it names the task and carries the traceback5Stale entity (ADR-083) A telemetry entity or stream stayed stale for exit_after_staleseconds. TheCRITICALlog line names it; check the device and the adapter behind it6Event loop stalled (ADR-088) The loop did not run for COSALETTE_LOOP_STALL_TIMEOUTseconds. Below theCRITICAL cosalette: event loop stalledline, stderr has every thread's stack; the one runningasyncio(not thecosalette-stallwatcher thread) shows the blocking call. Move it off the loop (asyncio.to_thread) or raise the timeout. A loop stuck in C code that holds the GIL exits with1instead, after twice the timeout: stderr starts withTimeout (H:MM:SS)!(for exampleTimeout (0:10:00)!) and has noCRITICALline; read the stacks the same way137OOM killed / SIGKILL Increase memory limit or reduce footprint - Image fails to build for arm64
-
Ensure BuildKit and QEMU are set up:
Related guides:
- Containerize Your Application — Dockerfile and multi-arch builds
- Deploy with Docker Compose — Compose configuration, health checks, and persistence
- Harden Your Deployment — security hardening and production logging