Skip to content

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), not localhost. Inside a container, localhost refers to the container itself. Verify name resolution with docker exec myapp getent hosts mosquitto.
MQTT reconnect loop with Connection reset by peer or an SSL error

The client may be attempting TLS against a broker that has no TLS listener. MYAPP_MQTT__TLS defaults to true since 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 log Client <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=false for a plaintext broker, or point the app at the broker's TLS listener (usually port 8883) and set MYAPP_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:

  1. Add device_cgroup_rules: ['c 188:* rmw'] under the service — scopes access to a single device major/minor (preferred).
  2. Map the specific device: devices: ['/dev/ttyUSB0:/dev/ttyUSB0'].
  3. Add the container user to the dialout group (group_add: [dialout]).

Do not reach for privileged: true to 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 use privileged: true in production.

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=WARNING to reduce log buffer pressure.
  • Use MemoryStore or NullStore instead of SqliteStore.
  • Run docker system prune to reclaim space from old images.
  • Consider adding a swap file on the host.
Container restarts in a loop

Check the exit code with docker inspect --format='{{.State.ExitCode}}' myapp:

Exit Code Meaning Action
1 Configuration error Check env vars — missing required field, invalid value
3 Runtime error Check logs with docker logs myapp for the root cause
4 Task failure (ADR-081) A device, telemetry, periodic or stream task kept failing. Search the logs for the CRITICAL line from cosalette._supervisor — it names the task and carries the traceback
5 Stale entity (ADR-083) A telemetry entity or stream stayed stale for exit_after_stale seconds. The CRITICAL log line names it; check the device and the adapter behind it
6 Event loop stalled (ADR-088) The loop did not run for COSALETTE_LOOP_STALL_TIMEOUT seconds. Below the CRITICAL cosalette: event loop stalled line, stderr has every thread's stack; the one running asyncio (not the cosalette-stall watcher 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 with 1 instead, after twice the timeout: stderr starts with Timeout (H:MM:SS)! (for example Timeout (0:10:00)!) and has no CRITICAL line; read the stacks the same way
137 OOM killed / SIGKILL Increase memory limit or reduce footprint
Image fails to build for arm64

Ensure BuildKit and QEMU are set up:

docker run --privileged --rm tonistiigi/binfmt --install arm64
docker buildx create --name pibuilder --use

Related guides: