| Status | Active |
|---|---|
| Owner | HyperFleet Platform Team |
| Last Updated | 2026-03-09 |
- Overview
- Base Images
- Multi-Stage Build Pattern
- Non-Root Users
- Go Build Parameters
- Container Labels
- .dockerignore
- Reference Dockerfile
- References
This document defines the standard conventions for Dockerfiles, base images, and container build practices across all HyperFleet service repositories.
- Consistent base images - All services use the same approved Red Hat UBI builder and runtime images, eliminating variation across Debian, Alpine, and other base images
- Security by default - Non-root users in all stages, enforced consistently across all repositories
- Reproducible builds - Standard version embedding and build flags across all services
- Konflux / Enterprise Contract compliance - All base images must come from allowed registry prefixes (
registry.access.redhat.com/,registry.redhat.io/) to passEnterpriseContractPolicychecks enforced by Konflux pipelines via Conforma (formerly Enterprise Contract). FIPS considerations documented and supported - Efficient builds - Multi-stage builds with layer caching, proper
.dockerignore, and minimal build context
This standard applies to all HyperFleet repositories that produce container images (repository type service in .hyperfleet.yaml).
All Go service Dockerfiles MUST use Red Hat UBI9 Go toolset as the builder image:
FROM registry.access.redhat.com/ubi9/go-toolset:1.25 AS builderWhy UBI9 Go toolset?
- Red Hat-supported and maintained
- FIPS-validated cryptographic libraries available
- Consistent with Red Hat OpenShift ecosystem
- Regular security updates
Note: The Go toolset image does not include
makeby default. Install it as root, then switch back to the non-root user (see Multi-Stage Build Pattern).
The default production runtime image MUST be Red Hat UBI9 Micro:
ARG BASE_IMAGE=registry.access.redhat.com/ubi9-micro:latest
FROM ${BASE_IMAGE}Using ARG BASE_IMAGE makes the runtime configurable for different build scenarios:
| Scenario | Base Image | Notes |
|---|---|---|
| Standard production | registry.access.redhat.com/ubi9-micro:latest |
Default. Minimal UBI with glibc, Red Hat supported |
FIPS-compliant (CGO_ENABLED=1) |
registry.access.redhat.com/ubi9-micro:latest |
Same image; includes glibc required for boringcrypto |
| Development / debugging | registry.access.redhat.com/ubi9/ubi-minimal:latest |
Includes shell and microdnf for troubleshooting |
All service Dockerfiles MUST use multi-stage builds with the following structure:
ARG BASE_IMAGE=registry.access.redhat.com/ubi9-micro:latest
# ── Builder stage ──
FROM registry.access.redhat.com/ubi9/go-toolset:1.25 AS builder
ARG GIT_SHA=unknown
ARG GIT_DIRTY=""
ARG BUILD_DATE=""
ARG APP_VERSION="0.0.0-dev"
USER root
RUN dnf install -y make && dnf clean all
WORKDIR /build
RUN chown 1001:0 /build
USER 1001
ENV GOBIN=/build/.gobin
RUN mkdir -p $GOBIN
COPY --chown=1001:0 go.mod go.sum ./
RUN --mount=type=cache,target=/opt/app-root/src/go/pkg/mod,uid=1001 \
go mod download
COPY --chown=1001:0 . .
RUN --mount=type=cache,target=/opt/app-root/src/go/pkg/mod,uid=1001 \
--mount=type=cache,target=/opt/app-root/src/.cache/go-build,uid=1001 \
CGO_ENABLED=0 GOOS=linux \
GIT_SHA=${GIT_SHA} GIT_DIRTY=${GIT_DIRTY} BUILD_DATE=${BUILD_DATE} \
make build
# ── Runtime stage ──
FROM ${BASE_IMAGE}
WORKDIR /app
# ubi9-micro doesn't include CA certificates; copy from builder for TLS (e.g. Google Pub/Sub)
COPY --from=builder /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
COPY --from=builder /build/bin/<service-name> /app/<service-name>
USER 65532:65532
EXPOSE 8080
ENTRYPOINT ["/app/<service-name>"]- CA certificates copied from builder stage —
ubi9-microdoesn't includeca-certificates; required for TLS connections to external services - Cache mounts for Go module and build caches to speed up rebuilds
go mod downloadas a separate layer before copying source for better layer caching--chown=1001:0on COPY commands for the builder stage (UBI9 convention)WORKDIR /appin the runtime stage to keep a clean layout- Build args passed through to
make buildfor version embedding
All container images MUST run as non-root users.
The UBI9 Go toolset image provides user 1001 by default. Temporarily switch to root only when installing system packages, then switch back:
USER root
RUN dnf install -y make && dnf clean all
WORKDIR /build
RUN chown 1001:0 /build
USER 1001Use the standard nonroot user 65532:65532:
USER 65532:65532| Value | When to Use | Runtime Image |
|---|---|---|
0 (default) |
Standard builds producing static binaries | ubi9-micro (default) |
1 |
FIPS-compliant builds with GOEXPERIMENT=boringcrypto |
ubi9-micro (includes glibc required for boringcrypto) |
Document this decision in your Dockerfile:
# CGO_ENABLED=0 produces a static binary. The default ubi9-micro runtime
# supports both static and dynamically linked binaries.
# For FIPS-compliant builds, use CGO_ENABLED=1 + GOEXPERIMENT=boringcrypto.All Go builds MUST include:
| Flag | Purpose |
|---|---|
-trimpath |
Remove local filesystem paths from binary for reproducibility |
-s -w (via -ldflags) |
Strip debug symbols to reduce binary size |
-X (via -ldflags) |
Embed version, commit hash, and build date |
Standard ldflags:
LDFLAGS := -s -w \
-X main.version=$(APP_VERSION) \
-X main.commit=$(GIT_SHA) \
-X main.date=$(BUILD_DATE)Container builds MUST specify the target platform:
PLATFORM ?= linux/amd64$(CONTAINER_TOOL) build --platform $(PLATFORM) ...All production images MUST include standardized OCI labels. Place the LABEL instruction at the end of the Dockerfile (after ARG re-declarations) so it doesn't invalidate earlier layer caches:
ARG APP_VERSION="0.0.0-dev"
LABEL name="<service-name>" \
vendor="Red Hat, Inc." \
version="${APP_VERSION}" \
summary="<one-line summary>" \
description="<detailed description of what the service does>"| Label | Description | Example |
|---|---|---|
name |
Service name (matches image name) | hyperfleet-sentinel |
vendor |
Organization | Red Hat, Inc. |
version |
Semantic version or git-derived version | abc1234 |
summary |
One-line description | HyperFleet Sentinel - Resource polling and event publishing service |
description |
Detailed description of the service | Watches HyperFleet API resources and publishes reconciliation events to message brokers |
All repositories producing container images MUST include a .dockerignore file at the repository root.
The reference .dockerignore is maintained as a standalone file: .dockerignore. Copy it into your repository root:
curl -sSL -o .dockerignore \
https://raw.githubusercontent.com/openshift-hyperfleet/architecture/main/hyperfleet/standards/.dockerignoreServices MAY append additional service-specific entries (e.g. generated code directories) after copying.
- Prevents
-buildvcserrors: Git metadata inside the build context causes failures with Go's VCS stamping during container builds - Reduces build context size: The
.git/directory can be large and is never needed inside the container - Faster builds: Smaller context means faster transfer to the Docker daemon
A complete reference Dockerfile incorporating all standards above. Replace <service-name> with the actual service binary name:
ARG BASE_IMAGE=registry.access.redhat.com/ubi9-micro:latest
FROM registry.access.redhat.com/ubi9/go-toolset:1.25 AS builder
ARG GIT_SHA=unknown
ARG GIT_DIRTY=""
ARG BUILD_DATE=""
ARG APP_VERSION="0.0.0-dev"
USER root
RUN dnf install -y make && dnf clean all
WORKDIR /build
RUN chown 1001:0 /build
USER 1001
ENV GOBIN=/build/.gobin
RUN mkdir -p $GOBIN
COPY --chown=1001:0 go.mod go.sum ./
RUN --mount=type=cache,target=/opt/app-root/src/go/pkg/mod,uid=1001 \
go mod download
COPY --chown=1001:0 . .
# CGO_ENABLED=0 produces a static binary. The default ubi9-micro runtime
# supports both static and dynamically linked binaries.
# For FIPS-compliant builds, use CGO_ENABLED=1 + GOEXPERIMENT=boringcrypto.
RUN --mount=type=cache,target=/opt/app-root/src/go/pkg/mod,uid=1001 \
--mount=type=cache,target=/opt/app-root/src/.cache/go-build,uid=1001 \
CGO_ENABLED=0 GOOS=linux \
GIT_SHA=${GIT_SHA} GIT_DIRTY=${GIT_DIRTY} BUILD_DATE=${BUILD_DATE} \
make build
FROM ${BASE_IMAGE}
WORKDIR /app
# ubi9-micro doesn't include CA certificates; copy from builder for TLS (e.g. Google Pub/Sub)
COPY --from=builder /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
COPY --from=builder /build/bin/<service-name> /app/<service-name>
USER 65532:65532
EXPOSE 8080
ENTRYPOINT ["/app/<service-name>"]
ARG APP_VERSION="0.0.0-dev"
LABEL name="<service-name>" \
vendor="Red Hat, Inc." \
version="${APP_VERSION}" \
summary="<one-line service summary>" \
description="<detailed service description>"The ubi9/go-toolset base image sets ENV VERSION=<go-toolchain-version> (e.g. 1.25.7). In Docker/Podman, an inherited ENV always shadows an ARG with the same name inside RUN commands. If a Dockerfile declares ARG VERSION and the Makefile uses VERSION ?=, the Go toolchain version leaks into the binary instead of the intended application version.
All HyperFleet Dockerfiles and Makefiles MUST use APP_VERSION instead of VERSION for the application version variable:
ARG APP_VERSION="0.0.0-dev"Since the Makefile uses APP_VERSION exclusively, the base image's ENV VERSION=<go-version> is harmless — nothing reads it. No ENV VERSION override is needed.
Makefile (APP_VERSION via git describe)
├─ go build -ldflags "-X ...Version=$(APP_VERSION)" → binary version (local builds)
└─ docker build --build-arg APP_VERSION=$(APP_VERSION) → Dockerfile
└─ ARG APP_VERSION → available in RUN commands
└─ make build → Makefile picks up APP_VERSION
| Scenario | APP_VERSION Value | Source |
|---|---|---|
| Untagged local build | a1b2c3d (or a1b2c3d-dirty) |
git describe --tags --always --dirty (abbreviated commit SHA) |
| Dev build from tagged repo | v0.1.1-3-gabcdef |
git describe --tags --always --dirty |
| Release build (at tag) | v0.1.1 |
git describe returns the tag automatically; explicit APP_VERSION=v0.1.1 is optional, useful for shallow clones where git describe may fail |
| No git metadata available | 0.0.0-dev |
Makefile fallback when git describe fails |
Raw go build (no Make) |
0.0.0-dev |
Source code default constant |
- Makefile Conventions - Standard Makefile targets and variables
- Directory Structure Standard - Standard repository layout