CalSync — Automate Outlook Calendar Colors

Auto-color-code events for your team using rules. Faster visibility, less admin. 10-user minimum · 12-month term.

CalSync Colors is a service by CPI Consulting

In this blog post Keep Docker Containers Running Prevent Exits in Production and Dev we will walk through why Docker containers exit and the reliable ways to keep them running.

At a high level, a container runs a single main process. When that process finishes, the container stops. This sounds simple, but it’s the root of most “my container keeps exiting” issues. The fix is usually to ensure the correct process is running in the foreground and to apply the right lifecycle controls for your environment.

We’ll start with the key concepts behind Docker’s process model, then move to practical steps, patterns, and common pitfalls. By the end, you’ll have a checklist to keep containers alive for development, CI, or production.

How Docker decides whether a container is “running”

Docker is built on Linux namespaces and cgroups and manages containers by tracking their PID 1—the first process inside the container. The Docker daemon considers a container “running” as long as PID 1 is alive. When PID 1 exits, Docker stops the container.

Important implications:

  • Your application (or the command you configure) must run in the foreground. If it daemonizes or backgrounds itself, the container will exit when the shell script ends.
  • CMD and ENTRYPOINT define what becomes PID 1. Overriding these at runtime changes what keeps the container alive.
  • PID 1 has special signal and zombie-reaping responsibilities. Use an init process when needed.

Quick checklist when a container exits immediately

  1. Inspect logs: docker logs <container>
  2. Check exit reason: docker inspect -f '{{.State.ExitCode}} {{.State.Error}} {{.State.OOMKilled}}' <container>
  3. Review the command/entrypoint actually used: docker inspect -f '{{.Path}} {{.Args}}' <container>
  4. Run the image interactively to reproduce: docker run --rm -it <image> sh

Proven ways to keep a container running

1) Make the main service run in the foreground

Many services (nginx, Apache, some app servers) default to daemon mode. In containers, you want the opposite: keep the service in the foreground so Docker can track it.

In a Dockerfile, encode this as the default CMD so you don’t need to remember it each time:

Other examples:

  • Apache HTTPD: httpd-foreground is the correct command provided by the official image.
  • Node.js: CMD ["node", "server.js"], and make sure server.js blocks (e.g., starts an HTTP server) instead of running a one-off script and exiting.

2) Use a long-running command for dev and debugging

Sometimes you want a “stay alive” container to exec into. Use a harmless long-running command:

These are great for development and debugging but are not a replacement for a properly foregrounded service in production.

3) Get CMD and ENTRYPOINT right

Prefer the JSON (exec) form to avoid shell quirks and signal-handling issues, and so that your process becomes PID 1 directly.

Also, avoid scripts that start your app with an ampersand (&). If a script backgrounds the main process and exits, the container stops. Keep the main process in the foreground or end with exec to replace the shell:

4) Use an init process for proper signal handling

PID 1 inside a container doesn’t automatically reap zombies or forward signals as you might expect. An init process solves this. The simplest option is Docker’s built-in --init flag (uses tini).

In Dockerfile or Compose, you can make it the default:

5) Apply restart policies for resilience

Restart policies don’t keep a healthy container “busy,” but they do recover from crashes, reboots, and transient failures.

Note: a constantly failing container will loop with a restart policy. Use logs and health checks to fix the root cause.

6) Keep interactive dev containers open

For hands-on work, start a shell and keep STDIN open with a TTY:

If you need it alive in the background, pair with a long-running command:

7) Running multiple processes? Use a supervisor—or split services

Best practice is one service per container. If you must run multiple processes (e.g., tiny agent + main app), use a lightweight supervisor or an init system and ensure the supervisor stays in the foreground.

If processes are unrelated, prefer splitting them into separate containers and connecting them via a network.

Common pitfalls that cause containers to exit

  • Daemonizing by default: Services like nginx or mysqld may exit unless configured to run in the foreground.
  • Shell scripts that end: A startup script that backgrounds processes and then exits will stop the container. Use exec or run the service in the foreground.
  • Override mistakes: docker run <image> bash overrides the Dockerfile’s CMD, so your app won’t start.
  • Health checks confusion: A failing HEALTHCHECK marks the container “unhealthy,” but doesn’t stop it by itself. Orchestrators may react to health status; Docker’s restart policy won’t automatically respond to health check failure.
  • OOM kills: If memory limits are too low, the kernel may kill your process. Check OOMKilled in docker inspect and raise limits.
  • Signal handling: If your app ignores SIGTERM, Docker will send SIGKILL after the timeout, which can corrupt state. Use an init process and graceful shutdown handlers.

Production-minded patterns

Foreground-first images

Build images that expose a single, foregrounded service with clear CMD/ENTRYPOINT. Avoid bash-centric entrypoints unless they add real value.

Include an init where necessary

Use --init or bundle tini/dumb-init so PID 1 behaves correctly, especially for applications that spawn workers.

Right-size resource limits

Set CPU and memory limits that match your workload. Include observability (metrics, logs) to catch restarts early.

Use restart policies and health checks together

Combine --restart unless-stopped with a HEALTHCHECK. While Docker doesn’t restart on health failure by itself, tooling and orchestrators can act on it, and you gain visibility.

Docker Compose equivalents

Compose lets you encode the “keep it running” patterns in one file.

Troubleshooting sequence when a container won’t stay up

  1. Confirm the real command: docker inspect -f '{{.Path}} {{.Args}}' <container>
  2. Read logs: docker logs -n 100 -f <container>
  3. Check exit causes: docker inspect -f '{{json .State}}' <container> | jq
  4. Try a shell in the image: docker run --rm -it <image> sh -lc '<your command>'
  5. Remove backgrounding and use exec in entrypoint scripts.
  6. Add --init and test signal handling (docker stop should exit cleanly).
  7. Apply restart policy only after the process reliably stays in the foreground.

Summary

Containers stop when their main process exits. To keep them running, ensure your service runs in the foreground, use proper CMD/ENTRYPOINT, consider an init process, and apply restart policies for resilience. For development, long-running commands like sleep infinity are convenient; for production, foreground-first images with correct signal handling are the gold standard.

If you need help hardening your container runtimes or standardizing images for your teams, the CloudProinc.com.au team works with organisations to make containers predictable, observable, and production-ready.


Discover more from CPI Consulting

Subscribe to get the latest posts sent to your email.