Exit Code 128 (Invalid Exit Argument) in Docker & Kubernetes
Docker documents 128 as an invalid exit argument: the process or its shell wrapper tried to return a status outside the 0-255 contract. Bash itself masks numeric values (exit 256 becomes 0 and exit -1 becomes 255) and exits 2 for a non-numeric argument, so a literal 128 usually means the application explicitly returned it or a supervisor rejected the status.
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}} | Error: {{.State.Error}}'Kubernetes state
kubectl get pod <pod_name> -o jsonpath='{range .status.containerStatuses[*]}{.name}{"\t"}{.lastState.terminated.reason}{"\tExit:"}{.lastState.terminated.exitCode}{"\n"}{end}'Signal check
kill -l 128 || echo "128 is not a signal"The important negative fact: there is no signal 0 that makes 128 a 128+n value. 128+n starts at 129 for SIGHUP. If the logs show no fatal signal and the status is exactly 128, treat it as an application or wrapper exit contract bug, not as a kernel kill.
Three production root causes
This is not 128 plus a signal. Docker documents 128 as an invalid exit argument; bash masks numeric values and exits 2 for a non-numeric one, so a literal 128 usually means the app returned it or a supervisor rejected the status. Validate and clamp values before returning them.
1. A wrapper passes an unvalidated status
exit $? is safe, but exit $(some_command) or exit $((code - 1)) can pass a negative or oversized value. Shells differ: bash masks a numeric value to 8 bits, so exit 256 succeeds as 0, while other supervisors reject the status and surface 128. Validate the value before returning it.
2. The application uses 128 as a sentinel
Teams sometimes reserve 128 for an internal category such as configuration rejected. Nothing received a fatal signal. Search the source for os.Exit(128), sys.exit(128), process.exit(128) or exit 128 in scripts before looking at the kernel.
3. The container runtime rejected an invalid status
An init wrapper or runtime that exits with -1 or a value above 255 can be mapped by the supervisor to 128. Check docker inspect .State.Error and the runtime logs. In Kubernetes, read lastState.terminated.message and reason for the exact failure.
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.
docker-compose.yml
A status-contract bug is deterministic; restart policies only repeat it. Run the container once in the foreground with the production command and echo $? to see the status the CLI receives.
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
deployment.yaml
Kubernetes propagates the container process status; it does not invent 128. If .lastState.terminated.exitCode is 128, the process or its wrapper returned it. The fix is in the image entrypoint, not in the pod spec.
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
replicas: 2
selector:
matchLabels:
app: app
template:
metadata:
labels:
app: app
spec:
terminationGracePeriodSeconds: 30
containers:
- name: app
image: ghcr.io/acme/app:1.4.2
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
Frequently asked questions
Is exit code 128 a signal?
No. 128+n is the shell convention for signal n, so 128 itself would imply signal 0, which does not exist. Docker documents 128 as an invalid exit argument. Confirm with kill -l 128 and inspect the entrypoint wrapper.
Why did exit 256 become 0?
POSIX exit statuses are 8 bits. Bash masks a numeric argument to the low 8 bits, so 256 becomes 0 and -1 becomes 255. A program that returns a large value may therefore look successful. Validate statuses before returning them instead of relying on truncation.
How should a container choose exit codes?
Return 0 for success and 1 for a generic application failure. Reserve 125-128 for Docker and shell failures, 129-192 (128 + signal) for signal termination, and 255 for SSH or explicit -1. Keep the mapping documented and avoid letting arbitrary internal error numbers escape to the process exit status.
Related exit codes, signals and network references
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