Initializing Enclave...

How to Fix Docker Container Stuck in 'Created' Status with tail -f /dev/null Entrypoint

Threat/Impact Level: MEDIUM | Downtime Risk: HIGH | Time to Fix: 5–15 mins


TL;DR

  • What broke: The container was created by the daemon but never transitioned to running — the runtime hit a pre-start failure (bad volume mount, missing network, OOM kill at fork, or a corrupted image layer) before the entrypoint process could be exec'd.
  • How to fix it: Run docker inspect <container_id> and docker logs <container_id> immediately. The State.Error field and OOMKilled flag will pinpoint the exact failure class. Fix the offending resource binding or image, then docker rm the dead container and re-run.
  • Sandbox: Use our Client-Side Sandbox below to auto-refactor your docker-compose.yml or Dockerfile — paste it in and get a corrected config without sending your secrets anywhere.

The Incident

You run docker ps -a and see this:

CONTAINER ID   IMAGE         COMMAND               CREATED         STATUS    PORTS   NAMES
a3f9c1d82b44   myapp:latest  "tail -f /dev/null"   3 minutes ago   Created           myapp_debug

Created is not a running state. It means the Docker daemon allocated the container filesystem and metadata but the runtime never called execve() on the entrypoint. The process does not exist. No PID. docker exec will fail with Error response from daemon: Container is not running.

This is a silent failure. No crash loop. No restart. Just a dead namespace sitting on disk.


The Attack Vector / Blast Radius

tail -f /dev/null is a placeholder entrypoint used to keep a container alive for manual exec access — common in debug sidecars, init containers, and CI job runners. The danger here is operational, not CVE-based, but the blast radius is real:

  1. CI/CD pipeline deadlock. A GitHub Actions or GitLab runner waiting on this container to become healthy will hang until the job timeout, burning minutes or hours of runner quota.
  2. Orchestrator confusion in Compose/Swarm. depends_on with condition: service_healthy will block dependent services indefinitely. Your entire stack fails to start with zero obvious error surfaced at the compose level.
  3. Volume lock contention. The created-but-never-started container holds a reference to its volume mounts. On some storage drivers (overlay2 with SELinux, NFS-backed volumes), this prevents other containers from acquiring a write lock on the same volume.
  4. Resource ghost. The container's network namespace and cgroup are allocated. On a dense host, hundreds of these ghost containers exhaust net.ipv4.ip_local_port_range and cgroup PIDs limits without a single process running.

How to Fix It

Step 1: Get the actual error

# This is the only command that matters first
docker inspect <container_id> --format='{{json .State}}' | jq .

Look for:

  • "Error": "<message>" — direct cause
  • "OOMKilled": true — host memory exhausted at fork time
  • "ExitCode": 128+ — signal-based kill before start
# Also pull daemon-level events
docker events --filter container=<container_id> --since 10m

Step 2: Common root causes and fixes

Cause A: Named volume or bind mount does not exist

# docker-compose.yml
services:
  debug:
    image: myapp:latest
-   volumes:
-     - /host/path/that/doesnt/exist:/app/data
+   volumes:
+     - ./data:/app/data  # ensure host path exists: mkdir -p ./data

Cause B: Network referenced does not exist

# docker-compose.yml
services:
  debug:
    image: myapp:latest
-   networks:
-     - backend_net  # network never declared or pre-created
+   networks:
+     - backend_net
+
+networks:
+  backend_net:
+    driver: bridge

Cause C: Image entrypoint override syntax error

# Dockerfile
- ENTRYPOINT tail -f /dev/null
+ ENTRYPOINT ["tail", "-f", "/dev/null"]
# Shell form does not receive signals correctly and can fail on minimal images
# (e.g., distroless, scratch) that have no /bin/sh

Cause D: Resource constraints killing the container at fork

# docker-compose.yml
services:
  debug:
    image: myapp:latest
    deploy:
      resources:
        limits:
-         memory: 4m   # too low — cgroup OOM fires before tail starts
+         memory: 64m

Basic Fix (one-liner cleanup and restart)

docker rm a3f9c1d82b44
docker inspect myapp:latest --format='{{.Config.Entrypoint}}'
# Verify the image has /usr/bin/tail available
docker run --rm myapp:latest which tail
docker-compose up -d debug

Enterprise Best Practice

Stop using tail -f /dev/null as a keep-alive. It is a footgun. Use a proper init process:

# Dockerfile
- ENTRYPOINT ["tail", "-f", "/dev/null"]
+ RUN apk add --no-cache tini
+ ENTRYPOINT ["/sbin/tini", "--"]
+ CMD ["sleep", "infinity"]
# tini reaps zombie processes, handles SIGTERM correctly,
# and works on distroless/minimal images via multi-stage copy

For debug sidecars in Kubernetes, use ephemeral containers instead of baking a placeholder entrypoint into your image:

kubectl debug -it <pod> --image=busybox:latest --target=<container>

💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.


Prevention in CI/CD

1. Hadolint in your pipeline (catches shell-form ENTRYPOINT)

# .github/workflows/lint.yml
- name: Lint Dockerfile
  uses: hadolint/[email protected]
  with:
    dockerfile: Dockerfile
    failure-threshold: warning
# DL3025 fires on shell-form CMD/ENTRYPOINT

2. Checkov for Compose resource constraint validation

checkov -f docker-compose.yml --check CKV_DOCKER_7,CKV_DOCKER_8

3. Healthcheck with timeout — fail fast, don't hang

# docker-compose.yml
services:
  debug:
    image: myapp:latest
    healthcheck:
      test: ["CMD", "pgrep", "tail"]
      interval: 5s
      timeout: 3s
      retries: 2
      start_period: 5s
    # Container moves to 'unhealthy' in 16s instead of hanging forever

4. OPA/Conftest policy — block placeholder entrypoints in production images

# policy/docker.rego
package docker

deny[msg] {
  input.Entrypoint[_] == "tail"
  msg := "Placeholder entrypoint 'tail' is not permitted in production images. Use tini or a real process."
}
conftest test docker-inspect-output.json --policy policy/

This gates your registry push and prevents the pattern from reaching production at all.

Related Diagnostics

"Part of the Performance Utility Matrix."

View all 219 Performance Tools →