Skip to content

Commit 54d8f5f

Browse files
committed
feat: enable multi-arch builds with ARM compatibility
Signed-off-by: Doug Edgar <dedgar@redhat.com>
1 parent 7721fcd commit 54d8f5f

File tree

6 files changed

+134
-26
lines changed

6 files changed

+134
-26
lines changed

.github/workflows/build-image.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,15 @@ jobs:
2121
with:
2222
go-version-file: go.mod
2323

24+
- name: Set up Docker Buildx
25+
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435
26+
2427
- name: Login to Quay.io
2528
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
2629
with:
2730
registry: quay.io
2831
username: ${{ secrets.APP_QUAY_USERNAME }}
2932
password: ${{ secrets.APP_QUAY_TOKEN }}
3033

31-
- name: Build and push latest image
32-
run: make image-build image-push -e IMG=quay.io/llamastack/llama-stack-k8s-operator:latest
34+
- name: Build and push multi-arch image
35+
run: make image-buildx -e IMG=quay.io/llamastack/llama-stack-k8s-operator:latest

.github/workflows/generate-release.yml

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,13 @@ jobs:
157157
run: |
158158
make test
159159
160+
- name: Set up Docker Buildx
161+
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435
162+
160163
- name: Validate build release image
161164
shell: bash
162165
run: |
163-
make image-build IMG=quay.io/llamastack/llama-stack-k8s-operator:v${{ steps.validate.outputs.operator_version }}
166+
make image-buildx IMG=quay.io/llamastack/llama-stack-k8s-operator:v${{ steps.validate.outputs.operator_version }}
164167
165168
- name: Commit and push changes
166169
shell: bash
@@ -247,10 +250,8 @@ jobs:
247250
with:
248251
go-version-file: go.mod
249252

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

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

262-
- name: Push release image
263+
- name: Build and push multi-arch release image
263264
shell: bash
264265
run: |
265-
make image-push IMG=quay.io/llamastack/llama-stack-k8s-operator:v${{ env.operator_version }}
266-
echo "✅ Pushed: quay.io/llamastack/llama-stack-k8s-operator:v${{ env.operator_version }}"
266+
make image-buildx IMG=quay.io/llamastack/llama-stack-k8s-operator:v${{ env.operator_version }}
267+
echo "✅ Pushed multi-arch image: quay.io/llamastack/llama-stack-k8s-operator:v${{ env.operator_version }}"
267268
268269
- name: Release complete
269270
shell: bash

.github/workflows/release-image.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,16 @@ jobs:
3030
with:
3131
ref: ${{ github.event.inputs.release_branch }}
3232

33+
- name: Set up Docker Buildx
34+
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435
35+
3336
- name: Login to Quay.io
3437
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
3538
with:
3639
registry: quay.io
3740
username: ${{ secrets.APP_QUAY_USERNAME }}
3841
password: ${{ secrets.APP_QUAY_TOKEN }}
3942

40-
- name: Build and push release image
43+
- name: Build and push multi-arch release image
4144
run: |
42-
make image -e IMG=quay.io/llamastack/llama-stack-k8s-operator:${{ github.event.inputs.version }}
45+
make image-buildx -e IMG=quay.io/llamastack/llama-stack-k8s-operator:${{ github.event.inputs.version }}

Dockerfile

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
# Build the manager binary
2+
# Supports multi-architecture builds: linux/amd64, linux/arm64, linux/s390x, linux/ppc64le
3+
# BUILDPLATFORM and TARGETPLATFORM are automatically set by Docker buildx for multi-arch builds
4+
ARG BUILDPLATFORM=linux/amd64
5+
ARG TARGETPLATFORM=linux/amd64
26
ARG GOLANG_VERSION=1.24
37

4-
FROM registry.access.redhat.com/ubi9/go-toolset:${GOLANG_VERSION} as builder
8+
FROM --platform=${BUILDPLATFORM} registry.access.redhat.com/ubi9/go-toolset:${GOLANG_VERSION} as builder
9+
# TARGETOS and TARGETARCH are automatically set by Docker buildx for multi-arch builds
510
ARG TARGETOS=linux
611
ARG TARGETARCH
712
ARG CGO_ENABLED=1
@@ -32,7 +37,7 @@ RUN CGO_ENABLED=${CGO_ENABLED} GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -a
3237

3338
# Use distroless as minimal base image to package the manager binary
3439
# Refer to https://github.com/GoogleContainerTools/distroless for more details
35-
FROM registry.access.redhat.com/ubi9/ubi-minimal:latest
40+
FROM --platform=${TARGETPLATFORM} registry.access.redhat.com/ubi9/ubi-minimal:latest
3641
WORKDIR /
3742
COPY --from=builder /workspace/manager .
3843
COPY --from=builder /workspace/controllers/manifests ./manifests/

Makefile

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ clean: ## Remove temporary files, caches, and downloaded tools
132132
@if command -v $(GOLANGCI_LINT) >/dev/null 2>&1; then \
133133
$(GOLANGCI_LINT) cache clean; \
134134
fi
135-
rm -f $(GOLANGCI_TMP_FILE) Dockerfile.cross cover.out
135+
rm -f $(GOLANGCI_TMP_FILE) Dockerfile.cross Dockerfile.podman.tmp Dockerfile.podman.*.tmp cover.out
136136
rm -rf $(LOCALBIN)
137137

138138
.PHONY: vet
@@ -179,21 +179,77 @@ image-push: ## Push image with the manager.
179179
$(CONTAINER_TOOL) push ${IMG}
180180

181181
# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple
182-
# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to:
182+
# architectures. (i.e. make image-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to:
183183
# - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/
184184
# - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/
185185
# - 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)
186186
# To adequately provide solutions that are compatible with multiple platforms, you should consider using this option.
187-
PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le
188-
.PHONY: docker-buildx
189-
docker-buildx: ## Build and push docker image for the manager for cross-platform support
190-
# copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile
191-
sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross
192-
- $(CONTAINER_TOOL) buildx create --name x-builder
193-
$(CONTAINER_TOOL) buildx use x-builder
194-
- $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross .
195-
- $(CONTAINER_TOOL) buildx rm x-builder
196-
rm Dockerfile.cross
187+
# Default platforms: linux/amd64,linux/arm64. To add more architectures, override PLATFORMS:
188+
# make image-buildx PLATFORMS=linux/amd64,linux/arm64,linux/s390x,linux/ppc64le IMG=...
189+
PLATFORMS ?= linux/amd64,linux/arm64
190+
# NOTE: Unlike image-build/image-push which are separate commands, image-buildx
191+
# combines both build and push operations. This coupling is necessary because:
192+
# 1. Multi-arch manifests require all platform-specific images to exist in the
193+
# registry before the manifest can reference them (unlike single-arch builds
194+
# which can be built locally and pushed later).
195+
# 2. Docker buildx's --push flag atomically builds all platforms, pushes them,
196+
# creates the manifest, and pushes the manifest in one operation.
197+
# 3. Podman's manifest approach requires platform images to be pushed before
198+
# they can be added to the manifest list.
199+
# Separating build and push for multi-arch would require complex state tracking
200+
# and intermediate steps that don't align with how buildx/manifest tools work.
201+
.PHONY: image-buildx
202+
image-buildx: ## Build and push docker/podman image for the manager for cross-platform support
203+
@if [ "$(CONTAINER_TOOL)" = "podman" ]; then \
204+
echo "Using podman for multi-arch build..."; \
205+
echo "Using podman manifest approach (podman buildx doesn't support --push)..."; \
206+
$(MAKE) podman-buildx-multiarch PLATFORMS=$(PLATFORMS) IMG=${IMG}; \
207+
else \
208+
echo "Using docker buildx for multi-arch build..."; \
209+
- $(CONTAINER_TOOL) buildx create --name x-builder --use || true; \
210+
$(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile .; \
211+
fi
212+
213+
.PHONY: podman-buildx-multiarch
214+
podman-buildx-multiarch: ## Build and push multi-arch images using podman native commands (fallback if buildx plugin unavailable)
215+
@echo "Building multi-arch images using podman manifest approach..."
216+
@manifest_name=$$(echo ${IMG} | sed 's|:.*||'); \
217+
manifest_tag=$$(echo ${IMG} | sed 's|.*:||'); \
218+
$(CONTAINER_TOOL) manifest rm $${manifest_name}:$${manifest_tag} 2>/dev/null || true; \
219+
$(CONTAINER_TOOL) manifest create $${manifest_name}:$${manifest_tag} || true; \
220+
for platform in $$(echo $(PLATFORMS) | tr ',' ' '); do \
221+
echo "Building for platform: $$platform"; \
222+
arch_tag=$${manifest_name}:$${manifest_tag}-$$(echo $$platform | tr '/' '-'); \
223+
os=$$(echo $$platform | cut -d'/' -f1); \
224+
arch=$$(echo $$platform | cut -d'/' -f2); \
225+
sed -e 's/^ARG BUILDPLATFORM=linux\/amd64/ARG BUILDPLATFORM/' \
226+
-e 's/^ARG TARGETPLATFORM=linux\/amd64/ARG TARGETPLATFORM/' \
227+
-e "s|\$${BUILDPLATFORM}|$$platform|g" \
228+
Dockerfile > Dockerfile.podman.$$(echo $$platform | tr '/' '-').tmp; \
229+
$(CONTAINER_TOOL) build --platform=$$platform --build-arg TARGETOS=$$os --build-arg TARGETARCH=$$arch -f Dockerfile.podman.$$(echo $$platform | tr '/' '-').tmp -t $$arch_tag .; \
230+
rm -f Dockerfile.podman.$$(echo $$platform | tr '/' '-').tmp; \
231+
$(CONTAINER_TOOL) push $$arch_tag; \
232+
$(CONTAINER_TOOL) manifest add $${manifest_name}:$${manifest_tag} $$arch_tag; \
233+
done; \
234+
$(CONTAINER_TOOL) manifest push --all $${manifest_name}:$${manifest_tag} docker://$${manifest_name}:$${manifest_tag}
235+
236+
.PHONY: image-build-arm
237+
image-build-arm: ## Build ARM64 image using podman/docker (single architecture)
238+
@# For podman, --platform automatically sets BUILDPLATFORM and TARGETPLATFORM
239+
@# For cross-compilation with CGO, we need the builder stage to use the target platform
240+
@# We create a temporary Dockerfile that fixes the platform references
241+
@if [ "$(CONTAINER_TOOL)" = "podman" ]; then \
242+
trap 'rm -f Dockerfile.podman.tmp' EXIT; \
243+
sed -e 's/^ARG BUILDPLATFORM=linux\/amd64/ARG BUILDPLATFORM/' \
244+
-e 's/^ARG TARGETPLATFORM=linux\/amd64/ARG TARGETPLATFORM/' \
245+
-e 's|\$${BUILDPLATFORM}|linux/arm64|g' \
246+
Dockerfile > Dockerfile.podman.tmp; \
247+
$(CONTAINER_TOOL) build --platform=linux/arm64 --build-arg TARGETOS=linux --build-arg TARGETARCH=arm64 -f Dockerfile.podman.tmp -t ${IMG} .; \
248+
rm -f Dockerfile.podman.tmp; \
249+
trap - EXIT; \
250+
else \
251+
$(CONTAINER_TOOL) build --platform=linux/arm64 --build-arg BUILDPLATFORM=linux/arm64 --build-arg TARGETPLATFORM=linux/arm64 --build-arg TARGETOS=linux --build-arg TARGETARCH=arm64 -t ${IMG} -f Dockerfile .; \
252+
fi
197253

198254
# Installer directory is updated to `release` from operator-sdk default `dist` directory.
199255
.PHONY: build-installer

README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,46 @@ Set `enabled: false` (or remove the block) to turn the feature back off; the ope
164164
The default image used is `quay.io/llamastack/llama-stack-k8s-operator:latest` when not supply argument for `make image`
165165
To create a local file `local.mk` with env variables can overwrite the default values set in the `Makefile`.
166166

167+
- Building multi-architecture images (ARM64, AMD64, etc.)
168+
169+
The operator supports building for multiple architectures including ARM64. To build and push multi-arch images:
170+
171+
```commandline
172+
make image-buildx IMG=quay.io/<username>/llama-stack-k8s-operator:<custom-tag>
173+
```
174+
175+
By default, this builds for `linux/amd64,linux/arm64`. You can customize the platforms by setting the `PLATFORMS` variable:
176+
177+
```commandline
178+
# Build for specific platforms
179+
make image-buildx IMG=quay.io/<username>/llama-stack-k8s-operator:<custom-tag> PLATFORMS=linux/amd64,linux/arm64
180+
181+
# Add more architectures (e.g., for future support)
182+
make image-buildx IMG=quay.io/<username>/llama-stack-k8s-operator:<custom-tag> PLATFORMS=linux/amd64,linux/arm64,linux/s390x,linux/ppc64le
183+
```
184+
185+
**Note**:
186+
- The `image-buildx` target works with both Docker and Podman. It will automatically detect which tool is being used.
187+
- For Docker: Multi-arch builds require Docker Buildx and may use QEMU emulation for cross-platform builds. Ensure Docker Buildx is set up:
188+
189+
```commandline
190+
docker buildx create --name x-builder --use
191+
```
192+
193+
- 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.
194+
- The resulting images are multi-arch manifest lists, which means Kubernetes will automatically select the correct architecture when pulling the image.
195+
196+
- Building ARM64-only images
197+
198+
To build a single ARM64 image (useful for testing or ARM-native systems):
199+
200+
```commandline
201+
make image-build-arm IMG=quay.io/<username>/llama-stack-k8s-operator:<custom-tag>
202+
make image-push IMG=quay.io/<username>/llama-stack-k8s-operator:<custom-tag>
203+
```
204+
205+
This works with both Docker and Podman.
206+
167207
- Once the image is created, the operator can be deployed directly. For each deployment method a
168208
kubeconfig should be exported
169209

0 commit comments

Comments
 (0)