Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/workflows/build-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@ jobs:
with:
go-version-file: go.mod

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435

- name: Login to Quay.io
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
registry: quay.io
username: ${{ secrets.APP_QUAY_USERNAME }}
password: ${{ secrets.APP_QUAY_TOKEN }}

- name: Build and push latest image
run: make image-build image-push -e IMG=quay.io/llamastack/llama-stack-k8s-operator:latest
- name: Build and push multi-arch image
run: make image-buildx -e IMG=quay.io/llamastack/llama-stack-k8s-operator:latest
17 changes: 9 additions & 8 deletions .github/workflows/generate-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,13 @@ jobs:
run: |
make test

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435

- name: Validate build release image
shell: bash
run: |
make image-build IMG=quay.io/llamastack/llama-stack-k8s-operator:v${{ steps.validate.outputs.operator_version }}
make image-buildx IMG=quay.io/llamastack/llama-stack-k8s-operator:v${{ steps.validate.outputs.operator_version }}
Copy link
Collaborator

@mfleader mfleader Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only want to build the image here as a part of the validation process, but not push it. From my understanding of the image-buildx rule that it also pushes the image. Would you create and use a Makefile rule here that encapsulates only the image building logic?


- name: Commit and push changes
shell: bash
Expand Down Expand Up @@ -247,10 +250,8 @@ jobs:
with:
go-version-file: go.mod

- name: Build release image
shell: bash
run: |
make image-build IMG=quay.io/llamastack/llama-stack-k8s-operator:v${{ env.operator_version }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435

- name: Log in to Quay.io
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
Expand All @@ -259,11 +260,11 @@ jobs:
username: ${{ secrets.APP_QUAY_USERNAME }}
password: ${{ secrets.APP_QUAY_TOKEN }}

- name: Push release image
- name: Build and push multi-arch release image
shell: bash
run: |
make image-push IMG=quay.io/llamastack/llama-stack-k8s-operator:v${{ env.operator_version }}
echo "✅ Pushed: quay.io/llamastack/llama-stack-k8s-operator:v${{ env.operator_version }}"
make image-buildx IMG=quay.io/llamastack/llama-stack-k8s-operator:v${{ env.operator_version }}
echo "✅ Pushed multi-arch image: quay.io/llamastack/llama-stack-k8s-operator:v${{ env.operator_version }}"

- name: Release complete
shell: bash
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/release-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,16 @@ jobs:
with:
ref: ${{ github.event.inputs.release_branch }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435

- name: Login to Quay.io
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
registry: quay.io
username: ${{ secrets.APP_QUAY_USERNAME }}
password: ${{ secrets.APP_QUAY_TOKEN }}

- name: Build and push release image
- name: Build and push multi-arch release image
run: |
make image -e IMG=quay.io/llamastack/llama-stack-k8s-operator:${{ github.event.inputs.version }}
make image-buildx -e IMG=quay.io/llamastack/llama-stack-k8s-operator:${{ github.event.inputs.version }}
42 changes: 33 additions & 9 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
# Build the manager binary
ARG GOLANG_VERSION=1.24

FROM registry.access.redhat.com/ubi9/go-toolset:${GOLANG_VERSION} as builder
# Use BUILDPLATFORM to run the builder natively (avoid QEMU emulation for Go compilation)
# This dramatically improves build reliability and speed for cross-platform builds
FROM --platform=$BUILDPLATFORM registry.access.redhat.com/ubi9/go-toolset:${GOLANG_VERSION} as builder
ARG TARGETOS=linux
ARG TARGETARCH
ARG CGO_ENABLED=1
ARG GOTAGS=strictfipsruntime,openssl
ARG BUILDPLATFORM
ARG TARGETPLATFORM

# FIPS compliance settings
# For native builds: CGO_ENABLED=1 with full FIPS OpenSSL support
# For cross-builds: CGO_ENABLED=0 with pure Go FIPS (strictfipsruntime)
ENV GOEXPERIMENT=strictfipsruntime

WORKDIR /workspace
Expand All @@ -24,19 +30,37 @@ COPY pkg/ pkg/
COPY distributions.json distributions.json

# Build the manager binary
# Cross-compilation is handled natively by Go via GOOS and GOARCH
# This runs on the build host's native architecture, not under QEMU emulation
USER root

# GOARCH is intentionally left empty to automatically detect the host architecture
# This ensures the binary matches the platform where image-build is executed
RUN CGO_ENABLED=${CGO_ENABLED} GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -a -tags=${GOTAGS} -o manager main.go
# Determine if we're cross-compiling by comparing BUILDPLATFORM and TARGETPLATFORM
# - Native builds (same platform): CGO_ENABLED=1 with openssl tag for full FIPS OpenSSL support
# - Cross builds (different platform): CGO_ENABLED=0 with pure Go FIPS (no CGO = no cross-compiler needed)
RUN echo "Building for TARGETPLATFORM=${TARGETPLATFORM} on BUILDPLATFORM=${BUILDPLATFORM}" && \
if [ "${BUILDPLATFORM}" = "${TARGETPLATFORM}" ]; then \
echo "Native build detected - using CGO_ENABLED=1 with OpenSSL FIPS"; \
CGO_ENABLED=1 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
go build -a -tags=strictfipsruntime,openssl -o manager main.go; \
else \
echo "Cross-compilation detected - using CGO_ENABLED=0 with pure Go FIPS"; \
CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
go build -a -tags=strictfipsruntime -o manager main.go; \
fi

# Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
# Use UBI minimal as the runtime base image
# This stage runs under QEMU for cross-platform builds, but the workload is minimal
FROM registry.access.redhat.com/ubi9/ubi-minimal:latest
ARG TARGETARCH

WORKDIR /
COPY --from=builder /workspace/manager .
COPY --from=builder /workspace/controllers/manifests ./manifests/
RUN microdnf install -y openssl && microdnf clean all

# Install openssl - use minimal options for reliability under QEMU emulation
RUN microdnf install -y --setopt=install_weak_deps=0 --setopt=tsflags=nodocs openssl && \
microdnf clean all

USER 1001

ENTRYPOINT ["/manager"]
40 changes: 30 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -179,21 +179,41 @@ image-push: ## Push image with the manager.
$(CONTAINER_TOOL) push ${IMG}

# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple
# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to:
# architectures. (i.e. make image-buildx IMG=myregistry/myoperator:0.0.1). To use this option you need to:
# - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/
# - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/
# - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=<myregistry/image:<tag>> then the export will fail)
# To adequately provide solutions that are compatible with multiple platforms, you should consider using this option.
PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le
.PHONY: docker-buildx
docker-buildx: ## Build and push docker image for the manager for cross-platform support
# copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile
sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross
- $(CONTAINER_TOOL) buildx create --name x-builder
# Note: The Dockerfile uses --platform=$BUILDPLATFORM for native cross-compilation, avoiding QEMU emulation
# for Go builds which dramatically improves reliability and speed.
PLATFORMS ?= linux/amd64,linux/arm64

.PHONY: image-buildx
image-buildx: ## Build and push multi-arch image with the manager (uses native cross-compilation)
@echo "Building multi-arch image for platforms: $(PLATFORMS)"
ifeq ($(CONTAINER_TOOL),docker)
- $(CONTAINER_TOOL) buildx create --name x-builder 2>/dev/null || true
$(CONTAINER_TOOL) buildx use x-builder
- $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross .
- $(CONTAINER_TOOL) buildx rm x-builder
rm Dockerfile.cross
$(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} .
else
# Podman: Use manifest-based multi-arch build
$(CONTAINER_TOOL) manifest rm ${IMG} 2>/dev/null || true
$(CONTAINER_TOOL) manifest create ${IMG}
@for platform in $$(echo $(PLATFORMS) | tr ',' ' '); do \
echo "Building for $$platform..."; \
$(CONTAINER_TOOL) build --platform $$platform --manifest ${IMG} . ; \
done
$(CONTAINER_TOOL) manifest push ${IMG}
endif
@echo "Successfully pushed multi-arch image: ${IMG}"

.PHONY: image-build-arm
image-build-arm: ## Build ARM64 image with the manager
$(CONTAINER_TOOL) build --platform linux/arm64 -t ${IMG} .

# Legacy docker-buildx target (deprecated, use image-buildx instead)
.PHONY: docker-buildx
docker-buildx: image-buildx ## Deprecated: use image-buildx instead

# Installer directory is updated to `release` from operator-sdk default `dist` directory.
.PHONY: build-installer
Expand Down
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,48 @@ Set `enabled: false` (or remove the block) to turn the feature back off; the ope
The default image used is `quay.io/llamastack/llama-stack-k8s-operator:latest` when not supply argument for `make image`
To create a local file `local.mk` with env variables can overwrite the default values set in the `Makefile`.

- Building multi-architecture images (ARM64, AMD64, etc.)

The operator supports building for multiple architectures including ARM64. To build and push multi-arch images:

```commandline
make image-buildx IMG=quay.io/<username>/llama-stack-k8s-operator:<custom-tag>
```

By default, this builds for `linux/amd64,linux/arm64`. You can customize the platforms by setting the `PLATFORMS` variable:

```commandline
# Build for specific platforms
make image-buildx IMG=quay.io/<username>/llama-stack-k8s-operator:<custom-tag> PLATFORMS=linux/amd64,linux/arm64

# Add more architectures (e.g., for future support)
make image-buildx IMG=quay.io/<username>/llama-stack-k8s-operator:<custom-tag> PLATFORMS=linux/amd64,linux/arm64,linux/s390x,linux/ppc64le
```

**Note**:
- The `image-buildx` target works with both Docker and Podman. It will automatically detect which tool is being used.
- **Native cross-compilation**: The Dockerfile uses `--platform=$BUILDPLATFORM` to run Go compilation natively on the build host, avoiding QEMU emulation for the build process. This dramatically improves build speed and reliability. Only the minimal final stage (package installation) runs under QEMU for cross-platform builds.
- **FIPS adherence**: Native builds use `CGO_ENABLED=1` with full OpenSSL FIPS support. Cross-compiled builds use `CGO_ENABLED=0` with pure Go FIPS (via `GOEXPERIMENT=strictfipsruntime`). Both approaches are Designed for FIPS.
- For Docker: Multi-arch builds require Docker Buildx. Ensure Docker Buildx is set up:

```commandline
docker buildx create --name x-builder --use
```

- For Podman: Podman 4.0+ supports `podman buildx` (experimental). If buildx is unavailable, the Makefile will automatically fall back to using podman's native manifest-based multi-arch build approach.
- The resulting images are multi-arch manifest lists, which means Kubernetes will automatically select the correct architecture when pulling the image.

- Building ARM64-only images

To build a single ARM64 image (useful for testing or ARM-native systems):

```commandline
make image-build-arm IMG=quay.io/<username>/llama-stack-k8s-operator:<custom-tag>
make image-push IMG=quay.io/<username>/llama-stack-k8s-operator:<custom-tag>
```

This works with both Docker and Podman.

- Once the image is created, the operator can be deployed directly. For each deployment method a
kubeconfig should be exported

Expand Down