Skip to content

feat: minimize gateway Docker image to reduce attack surface and pull size #1009

@dimityrmirchev

Description

@dimityrmirchev

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:

  1. Attack surface — a compromised container has access to package managers, shells, and system utilities that aid lateral movement.
  2. Image size — larger images slow cold-start pulls, especially in air-gapped or bandwidth-constrained environments.
  3. 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

  • I've reviewed existing issues and the architecture docs
  • This is a design proposal, not a "please build this" request

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions