PlaybookDocker exitsApproval-gated restart

Docker container keeps exiting: find the cause before restarting

Exit state, exit code, OOMKilled, logs, and restart policy tell different stories. Preserve the evidence before you create another crash loop.

Direct answer

When a Docker container keeps exiting, do not start with docker restart. List stopped containers, inspect the container state, read its exit code and OOMKilled flag, then capture the last logs and check host memory. Exit 137 can mean a SIGKILL and often points to memory pressure, but it is not proof of an OOM by itself. mttrly exposes read-only container status and log playbooks for this triage; restarting a container is a separate approve-required action that runs only after a human confirms it.

What you see

The same “Exited” status can represent a clean one-shot job, a configuration error, a killed process, or a dependency failure. Useful signals include:

docker ps -a shows Exited (1), Exited (137), Exited (143), or repeated restarts

The container starts and stops immediately without ever becoming healthy

Docker Compose reports a service as exited while nginx returns 502 or 503

Logs end with a stack trace, missing environment variable, connection refused, or permission denied

The State.OOMKilled flag is true, or host kernel logs contain an OOM-killer event

The container is still running but marked unhealthy — a different failure from an exited process

How to diagnose an exiting container manually

Capture the stopped state before changing it. Steps 1–7 are read-only; the final start or restart changes state.

1

List running and stopped containers

Use the container name, image, status, and age to identify the exact workload and whether it is crash-looping.

docker ps -a --no-trunc
2

Inspect the exit state

Read the exit code, OOMKilled flag, error string, and start/finish timestamps. Do not infer OOM from exit 137 alone.

docker inspect --format '{{json .State}}' my-container
3

Read logs from the stopped container

docker logs remains available after a container exits. Capture the final messages before log rotation removes them.

docker logs my-container --tail 200 --timestamps
4

Check restart policy and restart count

A restart policy can turn one deterministic failure into a noisy loop. It does not repair the underlying cause.

docker inspect --format 'restarts={{.RestartCount}} policy={{json .HostConfig.RestartPolicy}}' my-container
5

Check host memory and kernel OOM evidence

Correlate container state with current memory and kernel messages before concluding that the process was OOM-killed.

free -h
docker stats --no-stream
dmesg -T | grep -Ei "oom|killed process" | tail -20
6

Inspect the Compose service if applicable

Compose can show related services and preserve the service-level logs needed to find a failed dependency.

docker compose ps -a
docker compose logs --tail 200 my-service
7

Verify mounts and dependency reachability

Missing files, wrong permissions, and unavailable databases or queues commonly stop an otherwise valid image.

docker inspect --format '{{json .Mounts}}' my-container
docker network inspect my-network
8

Start again only after addressing the cause

After fixing configuration, permissions, resources, or dependencies, start the container and immediately verify state and logs.

docker start my-container
docker ps -a --filter name=my-container
docker logs my-container --tail 100

A restart is a test, not a root-cause fix. If the command, configuration, image, mount, secret, or dependency is still wrong, the container will exit again. Exit 137 means the process received SIGKILL; confirm State.OOMKilled or kernel OOM evidence before calling it a memory failure.

How mttrly handles it

mttrly does not currently ship a dedicated “docker-container-exits” diagnostic recipe. Instead, it exposes scoped Docker playbooks that an AI assistant or Telegram workflow can combine: inspect container state, list stopped containers, read bounded logs, correlate other read-only evidence, and only then request a restart.

1

container_status finds exited and unhealthy containers (read-only)

The playbook runs the mttrly Docker wrappers for docker ps -a and docker stats, then highlights rows containing Exited or unhealthy.

2

docker-ps-json can include stopped containers (read-only)

With all=true, the scoped playbook lists container names, images, states, and ports without changing Docker state.

3

docker-logs captures the selected container output (read-only)

The playbook accepts a validated container name and returns a bounded log tail — 50 lines by default, up to 500.

4

The assistant correlates evidence before suggesting action

It can combine the Docker output with other available read-only checks, such as memory_check, rather than interpreting an exit code in isolation.

5

restart_container is a separate approval-gated action

The playbook validates the container name, inspects it, restarts it, and lists container state. It does not run until the pending action is approved.

Example workflow

A realistic MCP session using the scoped Docker playbooks (values illustrative):

You -> Claude Code:
"The api container keeps exiting. Find the cause; do not restart it yet."

Claude -> mttrly:
- requests container_status
- requests docker-ps-json with all=true
- requests docker-logs for api

mttrly returns:
- api: Exited (1), not running
- other containers: healthy
- api logs: "ConfigurationError: DATABASE_URL is required"

Claude -> You:
"The container exits during application startup because DATABASE_URL is
missing. A restart before fixing that setting will fail the same way. Update
the deployment configuration, then I can request restart_container."

You: restore the required setting
Claude -> mttrly: requests restart_container for api
mttrly: creates a pending action and waits
You: review and approve the action
mttrly: inspects and restarts api, lists the resulting container state, and records the action

Remediation under runtime policy

restart_container — validates the container name, inspects the container, restarts it, and runs docker ps -a afterward. It is approve-required and never runs silently as part of read-only triage.

docker-service — can start, enable, or restart the Docker systemd service when the daemon itself is the failed layer. It is approve-required and is not a substitute for diagnosing one application container.

After a restart, check the new container state and logs. The restart_container result reports Docker state, but it does not prove that the application endpoint, database connection, or business workflow is healthy.

mttrly does not rewrite a Dockerfile, Compose file, application code, secrets, or restart policy during this workflow. Fix those at their source, then use a scoped action to restore service.

Diagnostics are read-only. Risky fixes follow runtime policy.

  • +mttrly investigates on its own: diagnostic recipes and read-only playbooks inspect the server without changing anything.
  • +Approval-required fixes create a pending action for confirmation from Telegram, the dashboard, or the IDE. Runtime policy — not the AI — decides the action class.
  • +A separately authorized Investigation session can let mttrly_execute_command skip per-action approval only within its server, time, and action limits. That bounded exception is visible and auditable.
  • +mttrly does not give an AI client an unrestricted production shell. Diagnostics, configured preauthorization, approval gates, and bounded sessions remain explicit.

These incident guides use the same scoped MCP action layer and can route approval decisions through the Telegram mobile workflow.

Detection tools tell you something is wrong. mttrly is the agent on your server that diagnoses the incident and prepares the fix — through scoped MCP tools, diagnostic recipes, and remediation playbooks. Approval-required actions normally wait for a human; any narrowly configured preauthorization remains bounded and audited. It complements monitoring like Grafana, Datadog, or UptimeRobot; it does not replace it.

When mttrly is not enough

Container evidence can isolate the failed layer, but the permanent fix may live elsewhere:

  • -A broken image, entrypoint, application build, or Docker Compose configuration must be fixed and redeployed from its source repository or deployment system.
  • -Missing secrets and external database, queue, DNS, or network failures require changes in the system that owns them.
  • -Kubernetes, ECS, Nomad, and other orchestrators have their own desired-state and restart controls; use the owning control plane instead of managing an individual Docker container behind it.
  • -If Docker, the host, or the mttrly agent is unreachable, use the provider console or a controlled SSH recovery path before live playbooks can run.

Frequently asked questions

Why does my Docker container exit immediately?

The container’s main process ended. Common causes include an invalid command, missing configuration, application startup error, unavailable dependency, permission problem, or forced termination. Inspect .State and read the stopped container’s logs before restarting it.

Does exit code 137 always mean out of memory?

No. Exit 137 means the process received SIGKILL. The kernel OOM killer is a common source, but a manual docker kill or another forced termination can produce the same code. Check the container’s OOMKilled flag and host kernel logs before concluding it was OOM.

Does mttrly have a dedicated recipe for exiting containers?

Not currently. mttrly exposes read-only container_status, docker-ps-json, and docker-logs playbooks that an assistant can combine for the diagnosis. This page deliberately describes that scoped workflow instead of claiming a dedicated recipe exists.

Will mttrly automatically restart a stopped container?

No. Read-only Docker checks do not change state. restart_container is an approve-required playbook: it creates a pending action and waits for a human to review and approve it before execution.

Does restart_container verify that my application is healthy?

It inspects and restarts the named container and lists container state afterward. That confirms the Docker-level result, not full application health. Verify the container logs and your actual health endpoint or user-facing request after the restart.

Related

Preserve the evidence before the next container restart

Connect your Linux server, inspect Docker state and logs through scoped tools, and keep every restart behind explicit human approval.