Containerization
Image Catalog
BitVault ships four build outputs. Three are deployable OCI images; the CLI is a signed static binary distributed separately.
| Image | Contents | Base Image |
|---|---|---|
| bitvaultd | Control plane: API gateway, Identity, File & Metadata, Storage, Sync, Sharing, Search, Notification, Billing, Admin modules | gcr.io/distroless/static or scratch |
| bitvault-worker | Async workers consuming NATS JetStream: indexer, GC/finalizer, outbox relay, notification dispatch | gcr.io/distroless/static or scratch |
| bitvault-web | Next.js SSR frontend (BFF layer) | gcr.io/distroless/nodejs |
| bitvault-cli | Signed static Go binary; distributed via GitHub Releases, Homebrew, direct download | Not a deploy artifact |
Image Design Rules
Every image produced by the BitVault build system adheres to these constraints:
- Multi-stage builds. A build stage with the full toolchain; a final stage with only the compiled artifact and its runtime dependencies. No build tools in the shipped layer.
- Distroless or scratch final stage.
gcr.io/distroless/staticfor Go binaries (no CGO);gcr.io/distroless/nodejsfor the web server. Noapt, no shell, no package manager in the final image. - Non-root UID. All containers run as a non-root user (
UID 65532for distroless; explicitUSERinstruction otherwise).runAsNonRoot: trueenforced by PodSecurity admission. - Read-only root filesystem.
readOnlyRootFilesystem: truein the security context. Any writable paths (e.g.,/tmp) areemptyDirvolume mounts. - All capabilities dropped.
capabilities.drop: [ALL]in the security context. No capability is added back. - seccomp RuntimeDefault.
seccompProfile.type: RuntimeDefaultapplied to every pod. - Reproducible builds. Source timestamps normalized; base image pinned by digest (not tag) in
FROMlines. Same source inputs produce a byte-identical layer hash. - Multi-arch. Every image is built for
linux/amd64andlinux/arm64via BuildKit--platform. A single manifest index is pushed to the registry. - No secrets in layers. BuildKit
--secretmounts are used for any credential needed at build time (e.g., private Go module proxy tokens). Secrets are neverENV,COPY, orADD-ed into a layer. - Layer ordering for cache efficiency. Dependencies (Go module cache,
node_modules) are copied and resolved before application source. Source changes invalidate only the final layers. - No in-image
HEALTHCHECK. Kubernetes liveness and readiness probes own the lifecycle. An in-imageHEALTHCHECKwould create a conflicting signal.
Tagging Strategy
| Tag form | Purpose |
|---|---|
@sha256:<digest> |
Primary deployment handle. Immutable. This is what ArgoCD and Helm values reference. |
v<major>.<minor>.<patch> |
Semantic version for human reference and release notes; points to same digest |
<git-sha> |
Traceability back to the exact commit; used in dev/preview auto-deploys |
latest |
Never used in any Kubernetes manifest or Helm values. Tags are for humans; digests are for machines. |
The digest is the promotion unit. The Helm values field is image.digest. CI opens a GitOps PR setting this field. No environment ever resolves latest.
Supply-Chain Properties
Every image published to the registry carries a full provenance chain verified at cluster admission:
| Property | Tool | What It Attests |
|---|---|---|
| SBOM | syft |
Complete software bill of materials (SPDX + CycloneDX) attached as OCI artifact |
| Vulnerability scan | trivy |
No unfixed CRITICAL/HIGH CVEs gate the merge; attached scan report as OCI artifact |
| Keyless cosign signature | cosign + Sigstore OIDC |
GitHub Actions OIDC identity → Fulcio short-lived cert → signature in Rekor transparency log |
| SLSA provenance | slsa-github-generator |
L2 provenance attestation recording build inputs, workflow, and runner |
Clusters run an admission webhook (e.g., cosign policy controller or Kyverno) that verifies the cosign signature and SLSA attestation before allowing a pod to schedule. An unsigned or unattested image is rejected at the gate.
Self-Host Parity
The same OCI images that run on Kubernetes are used under Docker Compose for self-hosted deployments. The deploy/compose/ directory ships docker-compose.{lite,standard,full}.yaml files that reference images by the same digest.
No separate “self-host build” exists. Self-host users consume the same signed, attested image from the public registry.
:::tip No shell access
Distroless images have no shell. Use kubectl debug with an ephemeral container (e.g., busybox or nicolaka/netshoot) attached to a running pod for runtime debugging. Structured JSON logs correlated by trace_id and rich OpenTelemetry traces significantly reduce the need to shell into a container in the first place.
:::