On this page
  1. Why does my container keep restarting?
  2. Step 1 — read the exit code and the last logs
  3. Step 2 — reproduce it interactively
  4. Step 3 — check the health check and the limits
  5. Step 4 — fix and prevent the loop
  6. Where AI coding assistants get this wrong
  7. Checklist
  8. FAQ
    1. Why does my container restart every 30 seconds exactly?
    2. What does exit code 137 mean?
    3. My container restarts and the logs are empty. Why?
    4. How do I test a fix for a restart loop?
  9. Related topics
  10. Sources
tutorial

Why Does My Container Keep Restarting Every 30 Seconds?

A restart loop has three usual causes: the process exits, a health check fails, or the runtime kills it. Read the exit code to tell them apart.

Quick answer

  • Read the exit code first. 0 means the process finished normally, 1 usually means an unhandled error, 137 means killed (often out of memory), 143 means terminated by SIGTERM.
  • A restart every 30 seconds with a healthy-looking process is usually a failing health or liveness check, not a crash.
  • A restart with no logs at all usually means the process exited before logging was configured, or the entrypoint command is wrong.
  • A restart under load, with 137, is the runtime killing the container for exceeding a memory limit.
  • Debug by running the same image interactively with the same environment variables and watching the process start.

Why does my container keep restarting?

Because the container’s main process ended. Docker and Kubernetes restart a container when its process exits, and the restart policy decides how. So the question is always why the process ended, and the exit code states the category. An exit code of 0 means it finished successfully — which still counts as a restart if the intended behaviour was to stay running. 1 or another low number means the application reported an error. 137 means the process was killed with SIGKILL, usually by the out-of-memory killer. 143 means SIGTERM, a graceful stop request.

The timing is a clue. A restart every thirty seconds, exactly, usually indicates a health check with a thirty-second interval and a failing container, or a loop in the application that exits and is restarted. A restart under increasing load points at memory or resource limits. A restart immediately on start points at configuration, a missing dependency, or a wrong command.

Step 1 — read the exit code and the last logs

Look at the container’s state, which records the exit code and when it happened, then at the logs from the final run rather than the whole history. The trap is that logs from a previous run can look reassuring while the current one failed before it logged anything. Find the boundary between runs and read what comes after it.

For a container managed by an orchestrator, the previous instance’s logs are the ones that matter, and the orchestrator can show them explicitly. If the state says the process was killed rather than exited, the container’s log is the wrong place to look — the reason is recorded in the runtime’s events or the kernel’s log.

# Docker: current state, exit code, restart count, and the last logs.
docker inspect --format \
  'exit={{.State.ExitCode}} oom={{.State.OOMKilled}} restarts={{.RestartCount}} status={{.State.Status}}' \
  my-container
docker logs --tail 50 my-container

# Kubernetes: previous-run logs and the reason for the last termination.
kubectl logs my-pod --previous --tail 50
kubectl get pod my-pod -o jsonpath=\
'{.status.containerStatuses[0].lastState.terminated.reason} {.status.containerStatuses[0].lastState.terminated.exitCode}\n'

# Look for OOM kills and health-check failures in events.
kubectl describe pod my-pod | tail -30
dmesg -T 2>/dev/null | grep -i 'killed process' | tail

Step 2 — reproduce it interactively

Run the image with the command overridden so you get a shell, and start the application by hand. That separates ‘the image is broken’ from ‘the command is wrong’. If the process starts fine by hand, the entrypoint or the arguments are the problem, which is common when a generated Dockerfile uses a shell form that does not expand the way the author assumed, or when the working directory differs from where the code expects to be.

Reproduce with the same environment as production. Missing environment variables are one of the most common causes of an immediate exit, and they are absent from an interactive run unless you pass them. Copy the production environment into the debugging session, or at least confirm which variables the application requires and whether each is set in the deployment.

# Override the entrypoint and start the app by hand, with prod-like env.
docker run --rm -it --entrypoint sh \
  --env-file ./prod.env \
  -p 8000:8000 \
  my-image

# Inside: run the actual start command and read the first error.
#   node dist/server.js
#   python -m gunicorn app:app --bind 0.0.0.0:8000
#   ./app

# Confirm it binds where the platform expects, not just localhost.
# A server bound to 127.0.0.1 is unreachable from the platform probe.

Step 3 — check the health check and the limits

A process that is running and healthy but keeps restarting is being failed by a check or killed by a limit. Health checks fail for a short list of reasons: the check targets the wrong port or path, the application needs longer to start than the initial delay allows, the check runs inside a container where the endpoint is not reachable on that address, or the check depends on a service that is not up yet. Compare the check’s timing against the application’s real startup time — a slow-starting application with an aggressive liveness probe restarts forever.

Limits fail differently. A container killed with 137 and an OOM flag exceeded its memory limit, whether that limit is in the deployment or on the host. A CPU limit that is too low can starve the process so it never becomes healthy and is restarted by a probe, which looks like a health problem but is a resource problem. Check the deployment’s requests and limits against what the process actually uses.

Step 4 — fix and prevent the loop

Fix the specific cause: correct the command or entrypoint, supply the missing variable, raise the memory limit or reduce the process’s usage, extend the startup grace period, or fix the health endpoint. Then verify by watching the restart count stay flat for longer than the longest observed interval — a loop with a thirty-second period is not disproved by two minutes of quiet.

Prevent the recurrence by making the deployment state its assumptions: bind to all interfaces, log to standard output so the platform can capture it, fail fast on a missing required variable with a clear message, and give the process a startup probe that reflects its real cold-start time. Those are the small choices that turn a restart loop with no output into an error message that names the problem.

Where this bites vibecoders

Generated Dockerfiles and deployments repeatedly get the same four things wrong: they bind the server to localhost so the platform’s probe cannot reach it, they run the process as root, they omit the health endpoint the orchestrator expects, and they set a memory limit that the runtime’s default heap will exceed. Each produces a restart loop with weak logs. Comparing a generated deployment against those four points is a fast review, and it prevents most loops before the first deploy.

Where AI coding assistants get this wrong

  • Binding the server to 127.0.0.1, making it unreachable from the platform’s health probe.
  • Using a liveness check with a start delay shorter than the application’s real startup time.
  • Running the container as root by default and hitting permission or security policies.
  • Setting a container memory limit below the runtime’s default heap size.
  • Reading only the current logs and missing the failure in the previous run.
  • Restarting policy set to always on a one-shot job that is meant to exit.

Checklist

  • Read the exit code, the restart count, and whether the process was OOM-killed.
  • Read the logs for the final run only.
  • Run the image interactively and start the application by hand.
  • Confirm every required environment variable is present in the deployment.
  • Check the health check’s port, path, and timing against real startup time.
  • Compare memory and CPU limits with actual usage.

FAQ

Why does my container restart every 30 seconds exactly?

That period usually matches a health check interval or timeout. The process is either failing the check or being killed just after it starts, and the container runtime restarts it each cycle. Look at the health check configuration and the previous run’s logs, and compare the application’s real startup time with the check’s initial delay.

What does exit code 137 mean?

It means the process was terminated with SIGKILL, most often because the container exceeded its memory limit and the runtime or kernel killed it. Check whether the container is flagged as out-of-memory, then compare the process’s memory use with the configured limit. A heap limit set above the container limit will trigger this under load.

My container restarts and the logs are empty. Why?

The process exited before it produced any output, so the failure is in setup rather than in the application. Common causes are a wrong entrypoint, a missing environment variable, an unresolvable dependency, or a bind to an interface that does not exist. Running the image interactively and starting the process by hand surfaces the error immediately.

How do I test a fix for a restart loop?

Watch the restart count stay flat for longer than the longest interval you observed, ideally under real traffic. A loop with a thirty-second period is not disproved by a couple of quiet minutes, because the trigger may be the check timing or a load-dependent memory spike. Monitor for at least several cycles before calling it fixed.

Sources

Share: