Exit code 2 Warning Docker & Kubernetes

Exit Code 2 (Shell Misuse) in Docker & Kubernetes

Verdict · 2 = shell syntax error or misuse of a builtin

Exit code 2 is the shell's usage or syntax status. The interpreter rejected the script or a builtin received invalid arguments before your application started. No signal was delivered and 128+n does not apply; the error is in the entrypoint text itself.

Observed exit2
Signal / meaningShell Misuse
Default actionWarning

Proof commands: confirm the exit status and the killer

Run these before changing anything. The first two identify what the runtime recorded; the third finds a kernel-level kill on the host. Replace the placeholder container ID, pod name or host path with your own.

Docker inspect

docker inspect <container_id> --format '{{.State.Status}} | ExitCode: {{.State.ExitCode}} | OOMKilled: {{.State.OOMKilled}}'

Kubernetes state

kubectl get pod <pod_name> -o jsonpath='{range .status.containerStatuses[*]}{.name}{"\t"}{.lastState.terminated.reason}{"\tExit:"}{.lastState.terminated.exitCode}{"\n"}{end}'

Syntax check

docker run --rm --entrypoint /bin/sh <image> -n /entrypoint.sh

The logs usually contain a line such as syntax error: unexpected end of file, $'\r': command not found, or /entrypoint.sh: 12: Syntax error: "then" unexpected. Those messages come from the shell and are the proof. Exit code 2 from a compiled application can also be an application-defined code, so confirm the entrypoint is a shell script first.

Three production root causes

The shell could not run the entrypoint as written. Common causes are an unclosed if/fi or quote, Windows CRLF line endings, or bash syntax such as [[ ]] under Alpine's /bin/sh. Run sh -n on the script and read the first line of docker logs.

1. An unclosed block or quote in the entrypoint

A missing fi, done, or closing quote makes the whole script invalid; most shells report it at EOF and return 2. The same script may still run on a developer machine if a newer shell is more permissive, so validate with the exact interpreter in the image: sh -n or bash -n inside the container.

2. Windows CRLF line endings

A checkout with core.autocrlf=true turns #!/bin/sh into #!/bin/sh\r. The kernel then looks for an interpreter named /bin/sh\r and the shell reports a bad interpreter or a stray $'\r' token. Enforce LF with a .gitattributes rule and strip CRs in the Docker build, not only in the local checkout.

3. Bash syntax running under /bin/sh

Alpine uses BusyBox ash and Debian/Ubuntu images use dash; both reject [[ ]], arrays, source and process substitution. Distroless images have no shell at all, so an entrypoint that needs /bin/sh fails earlier with 127. A CI script tested with bash fails with exit 2 when the image runs it with sh. Either install bash with apk add --no-cache bash and use an explicit bash shebang, or rewrite the script in POSIX sh.

Copy-ready fixes for Docker Compose and Kubernetes

The two manifests set explicit memory limits and a termination grace period. Use them as a baseline, then adjust the values to the measured working set of the service.

Compose

docker-compose.yml

Do not paper over a broken entrypoint with restart: always. The same syntax error will repeat on every attempt. Validate the command locally with docker compose config and run the image once with the production entrypoint before scaling it.

services:
  app:
    image: ghcr.io/acme/app:1.4.2
    restart: on-failure:3
    stop_grace_period: 10s
    deploy:
      resources:
        limits:
          memory: 256M
        reservations:
          memory: 128M
Kubernetes

deployment.yaml

Kubernetes does not repair entrypoint syntax. If you override command or args, remember that they replace the image values rather than append to them, and keep the shell explicit: ["/bin/sh", "-c", "..."].

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: app
  template:
    metadata:
      labels:
        app: app
    spec:
      terminationGracePeriodSeconds: 20
      containers:
        - name: app
          image: ghcr.io/acme/app:1.4.2
          resources:
            requests:
              cpu: "100m"
              memory: "128Mi"
            limits:
              cpu: "500m"
              memory: "256Mi"
Dockerfile

Strip CRLF and validate at build time

This turns a deployment-time shell error into a failing build.

FROM alpine:3.20
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN sed -i 's/\r$//' /usr/local/bin/entrypoint.sh \
 && chmod +x /usr/local/bin/entrypoint.sh \
 && /bin/sh -n /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]

Frequently asked questions

What does exit code 2 mean in a Docker container?

It usually means the shell that runs the entrypoint rejected the script or its arguments. Syntax errors, CRLF line endings and bash-only syntax under /bin/sh are the common causes. It can also be an application-defined exit status, so check whether the image entrypoint is a shell script.

How do I test an entrypoint before deploying?

Run the exact interpreter against the script inside the final image: docker run --rm --entrypoint /bin/sh <image> -n /entrypoint.sh. Then run the image once with its production command and environment. A syntax check catches missing fi, done and quotes but not missing runtime files.

Why does the script work locally but fail in the container?

The image may use dash or BusyBox ash instead of bash, the working directory may differ, or the file may have CRLF endings after a Windows checkout. Test with the image's own shell and line endings; do not assume the developer machine's bash is representative.

Follow the status you actually observed, then check the related port or calculator when the failure involves networking, exposure or firewall rules.

Reference basis: POSIX signal semantics (128 + n), Docker container exit status conventions, and Kubernetes container termination behaviour. Verify the exact behaviour against your runtime version before relying on it in an incident.

Last reviewed 2026-11-06 · All exit codes · Developer tools · Privacy