This error occurs when running containers built FROM scratch because scratch images contain absolutely nothing - no shell, no libraries, no system files. You must use exec form for CMD/ENTRYPOINT and ensure your binary is statically compiled.
This error indicates that Docker is trying to execute a command using `/bin/sh` (the shell), but the container has no shell installed. This is most common with images built `FROM scratch`, which is a completely empty base image containing no files whatsoever. When you use the "shell form" of CMD or ENTRYPOINT in your Dockerfile (e.g., `CMD myapp --start`), Docker actually runs it as `/bin/sh -c "myapp --start"`. Since scratch images have no shell, this fails immediately. The scratch base image is designed for creating minimal containers that contain only your application binary. It provides the smallest possible image size and attack surface, but requires that: 1. Your binary is statically compiled (no external library dependencies) 2. All commands use "exec form" (JSON array syntax) instead of "shell form" 3. You don't attempt to use RUN commands in the Dockerfile after the FROM scratch line
The most common fix is to use JSON array syntax (exec form) instead of string syntax (shell form):
# WRONG - Shell form tries to use /bin/sh
FROM scratch
COPY myapp /myapp
CMD /myapp
# CORRECT - Exec form runs binary directly
FROM scratch
COPY myapp /myapp
CMD ["/myapp"]
# Also correct with arguments
CMD ["/myapp", "--port", "8080"]The exec form CMD ["/myapp"] executes the binary directly via the kernel's execve() syscall without needing a shell. The shell form CMD /myapp is interpreted as /bin/sh -c "/myapp", which fails without a shell.
Your binary must have no runtime dependencies on shared libraries. For Go applications:
# Check if your binary is statically linked
ldd myapp
# Should output: "not a dynamic executable" or "statically linked"
# If it shows dependencies, rebuild statically
CGO_ENABLED=0 go build -ldflags="-s -w" -o myapp .For other languages:
# C/C++ - use static linking
gcc -static -o myapp myapp.c
# Rust - static linking is default for musl targets
rustup target add x86_64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-muslIf ldd shows any dependencies like libc.so.6, the binary won't work in a scratch image.
CGO enables Go to call C code, but this creates dynamic library dependencies. Disable it for scratch images:
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
# CGO_ENABLED=0 is crucial for scratch images
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /myapp .
FROM scratch
COPY --from=builder /myapp /myapp
CMD ["/myapp"]Common build flags for Go:
- CGO_ENABLED=0 - Disable CGO for pure Go compilation
- -ldflags="-s -w" - Strip debug info for smaller binary
- GOOS=linux - Cross-compile for Linux if building on Mac/Windows
If you need shell functionality, use a minimal image like Alpine, BusyBox, or distroless:
# Option 1: Alpine (~5MB) - has shell and package manager
FROM alpine:3.19
COPY myapp /myapp
CMD ["/myapp"]
# Option 2: BusyBox (~1MB) - has shell, minimal tools
FROM busybox:1.36
COPY myapp /myapp
CMD ["/myapp"]
# Option 3: Distroless (~2MB) - no shell but has C libraries
FROM gcr.io/distroless/static-debian12
COPY myapp /myapp
CMD ["/myapp"]Image size comparison:
- scratch: 0 bytes (truly empty)
- distroless/static: ~2MB
- busybox: ~1MB
- alpine: ~5MB
Scratch images don't have CA certificates, so HTTPS requests will fail. Copy them from your build stage:
FROM golang:1.21 AS builder
# ... build steps ...
FROM scratch
# Copy CA certificates for HTTPS
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# Copy timezone data if needed
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /myapp /myapp
CMD ["/myapp"]Alternatively, copy from Alpine:
FROM alpine:3.19 AS certs
RUN apk add --no-cache ca-certificates
FROM scratch
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/If you're unsure why your scratch image fails, add a debug stage that uses Alpine to test the binary:
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o /myapp .
# Debug stage - use this to troubleshoot
FROM alpine:3.19 AS debug
COPY --from=builder /myapp /myapp
RUN ldd /myapp || echo "Statically linked"
CMD ["/myapp"]
# Production stage
FROM scratch AS production
COPY --from=builder /myapp /myapp
CMD ["/myapp"]Build and test each stage:
# Test debug stage
docker build --target debug -t myapp:debug .
docker run myapp:debug
# Build production stage
docker build --target production -t myapp:prod .If you need to debug a running scratch container, you can copy busybox into it:
# Get busybox binary
docker create --name busybox busybox
docker cp busybox:/bin/busybox ./busybox
docker rm busybox
# Copy into running container
docker cp ./busybox mycontainer:/busybox
# Now you can exec with busybox
docker exec -it mycontainer /busybox shOr include busybox in your image during development:
FROM busybox AS busybox
FROM scratch
COPY --from=busybox /bin/busybox /busybox
COPY myapp /myapp
CMD ["/myapp"]
# Debug with: docker exec -it container /busybox shRemove the busybox lines for production builds.
### Why Scratch Images Exist
The scratch image is designed for:
- Minimal attack surface: No shell means no shell exploits
- Smallest possible size: Only your binary, nothing else
- Compliance: Some security policies require minimal base images
- Fast startup: Less filesystem overhead
### Common Multi-Architecture Issues
When building for multiple architectures, ensure your binary matches the target:
FROM --platform=$BUILDPLATFORM golang:1.21 AS builder
ARG TARGETOS TARGETARCH
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /myapp .
FROM scratch
COPY --from=builder /myapp /myapp
CMD ["/myapp"]Build for multiple platforms:
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest .### The "no such file" Error with Correct Exec Form
If you're using exec form but still getting this error, your binary likely has dynamic library dependencies. Check with:
# In your builder stage
docker run --rm builder-image ldd /myappCommon culprits:
- net package in Go: Use CGO_ENABLED=0 or -tags netgo
- sqlite3 in Go: Requires CGO - use a different database or pure Go driver
- glibc vs musl: Build with musl for truly static binaries
### Security Considerations
Scratch images are excellent for security:
# Scanning a scratch image for vulnerabilities
docker scout cves myapp:scratch
# Typically shows 0 vulnerabilities since there's no OS
# vs scanning an alpine image
docker scout cves myapp:alpine
# May show OS-level CVEsHowever, ensure your application handles signals correctly (no init process in scratch):
// Handle graceful shutdown in Go
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)image operating system "linux" cannot be used on this platform
How to fix 'image operating system linux cannot be used on this platform' in Docker
manifest unknown: manifest unknown
How to fix 'manifest unknown' in Docker
cannot open '/etc/passwd': Permission denied
How to fix 'cannot open: Permission denied' in Docker
Error response from daemon: failed to create the ipvlan port
How to fix 'failed to create the ipvlan port' in Docker
toomanyrequests: Rate exceeded for anonymous users
How to fix 'Rate exceeded for anonymous users' in Docker Hub