Problem Statement
The gateway runtime image is based on nvcr.io/nvidia/base/ubuntu:noble-20251013, a full Ubuntu userspace. While the multi-stage build already avoids shipping build tools, the final image still carries unnecessary OS packages, shells, and utilities that increase:
- Attack surface — a compromised container has access to package managers, shells, and system utilities that aid lateral movement.
- Image size — larger images slow cold-start pulls, especially in air-gapped or bandwidth-constrained environments.
- CVE noise — distro packages accumulate vulnerabilities unrelated to the gateway binary, increasing scanner findings and maintenance burden.
Proposed Design
1. Switch base image to distroless
Replace nvcr.io/nvidia/base/ubuntu:noble with gcr.io/distroless/cc-debian13:nonroot.
This image includes glibc and libgcc — sufficient for the dynamically-linked Rust binary (built with x86_64-unknown-linux-gnu / aarch64-unknown-linux-gnu targets) — but ships no shell, no package manager, and no unnecessary utilities. It is ~20 MB compressed vs ~80 MB for the Ubuntu base.
A follow-up issue can evaluate switching to static-debian13 with a musl-linked binary for an even smaller footprint (~2 MB base), but that requires target/toolchain changes and broader testing.
2. Remove unnecessary migrations COPY
The current Dockerfile copies crates/openshell-server/migrations/ into the final image. This is unnecessary — migrations are embedded into the binary at compile time via sqlx::migrate!("./migrations/sqlite") and sqlx::migrate!("./migrations/postgres") proc macros. Removing this COPY eliminates dead weight from the image.
3. Minimal final stage
FROM gcr.io/distroless/cc-debian13:nonroot AS gateway
COPY --from=gateway-binary /build/out/openshell-gateway /usr/local/bin/
ENTRYPOINT ["openshell-gateway"]
CMD ["--port", "8080"]
Optionally the openshell user (UID 1000) can be preserved.
CA certs are included in the distroless base. No other runtime files are needed.
Alternatives Considered
distroless/static-debian13 — smallest possible (~2 MB) but requires switching to x86_64-unknown-linux-musl and verifying all crate dependencies work under musl. Better as a follow-up.
- Alpine-based image — smaller than Ubuntu but still ships a shell, package manager, and introduces musl quirks (DNS resolver, stack size defaults).
- Keep Ubuntu, remove packages — fragile (apt remove can break dep chains), doesn't eliminate shell or package manager, still large.
- Chainguard
cgr.dev/chainguard/cc-dynamic — signed and SBOM-attached but adds an external registry dependency.
Agent Investigation
Current state of deploy/docker/Dockerfile.images gateway stage:
- Multi-stage build already separates builder from runtime.
- Final stage:
FROM nvcr.io/nvidia/base/ubuntu:noble-20251013 AS gateway
- Installs
ca-certificates and upgrades gpgv at runtime.
- Copies binary + migrations (migrations COPY is unnecessary — embedded via
sqlx::migrate!()).
- Runs as
openshell user (UID 1000).
- Debug symbols already stripped (
strip = true in [profile.release]).
- Binary is dynamically linked against glibc (
*-unknown-linux-gnu targets in cross-build.sh).
Estimated compressed image size: current ~100+ MB → ~30-40 MB with cc-debian13 + removing dead COPY.
Checklist
Problem Statement
The gateway runtime image is based on
nvcr.io/nvidia/base/ubuntu:noble-20251013, a full Ubuntu userspace. While the multi-stage build already avoids shipping build tools, the final image still carries unnecessary OS packages, shells, and utilities that increase:Proposed Design
1. Switch base image to distroless
Replace
nvcr.io/nvidia/base/ubuntu:noblewithgcr.io/distroless/cc-debian13:nonroot.This image includes glibc and libgcc — sufficient for the dynamically-linked Rust binary (built with
x86_64-unknown-linux-gnu/aarch64-unknown-linux-gnutargets) — but ships no shell, no package manager, and no unnecessary utilities. It is ~20 MB compressed vs ~80 MB for the Ubuntu base.A follow-up issue can evaluate switching to
static-debian13with a musl-linked binary for an even smaller footprint (~2 MB base), but that requires target/toolchain changes and broader testing.2. Remove unnecessary migrations COPY
The current Dockerfile copies
crates/openshell-server/migrations/into the final image. This is unnecessary — migrations are embedded into the binary at compile time viasqlx::migrate!("./migrations/sqlite")andsqlx::migrate!("./migrations/postgres")proc macros. Removing this COPY eliminates dead weight from the image.3. Minimal final stage
Optionally the
openshelluser (UID 1000) can be preserved.CA certs are included in the distroless base. No other runtime files are needed.
Alternatives Considered
distroless/static-debian13— smallest possible (~2 MB) but requires switching tox86_64-unknown-linux-musland verifying all crate dependencies work under musl. Better as a follow-up.cgr.dev/chainguard/cc-dynamic— signed and SBOM-attached but adds an external registry dependency.Agent Investigation
Current state of
deploy/docker/Dockerfile.imagesgateway stage:FROM nvcr.io/nvidia/base/ubuntu:noble-20251013 AS gatewayca-certificatesand upgradesgpgvat runtime.sqlx::migrate!()).openshelluser (UID 1000).strip = truein[profile.release]).*-unknown-linux-gnutargets incross-build.sh).Estimated compressed image size: current ~100+ MB → ~30-40 MB with
cc-debian13+ removing dead COPY.Checklist