feat(docker): support native multi-arch (amd64/arm64) builds (#5096)
- Update Dockerfile.base to exclude prepackaged cache on arm64/aarch64
and trigger native SDK bootstrapping.
- Exclude .pub-preload-cache by default on all architectures to optimize
size.
- Update Dockerfile to download yq dynamically based on target
architecture.
- Refactor cloudbuild.yaml to use docker buildx with QEMU emulation and
direct manifest-list pushing.
- Remove hardcoded amd64 platform constraints from docker-compose.yml.
- Update README.md to document arm64 capabilities and simplify local
commands.
fixes: flutter/flutter#188873
diff --git a/cloud_build/flutter_agy_docker/Dockerfile b/cloud_build/flutter_agy_docker/Dockerfile
index 140f144..09d62e1 100644
--- a/cloud_build/flutter_agy_docker/Dockerfile
+++ b/cloud_build/flutter_agy_docker/Dockerfile
@@ -31,7 +31,8 @@
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null; \
apt-get update && apt-get install -y gh; \
# install yq, a YAML/XML/TOML processor like jq
- wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64; \
+ ARCH=$(dpkg --print-architecture); \
+ wget -qO /usr/local/bin/yq "https://github.com/mikefarah/yq/releases/latest/download/yq_linux_${ARCH}"; \
chmod a+x /usr/local/bin/yq; \
locale-gen en_US.UTF-8; \
ln -s $(which fdfind) /usr/local/bin/fd; \
diff --git a/cloud_build/flutter_agy_docker/Dockerfile.base b/cloud_build/flutter_agy_docker/Dockerfile.base
index 74a895a..a67085c 100644
--- a/cloud_build/flutter_agy_docker/Dockerfile.base
+++ b/cloud_build/flutter_agy_docker/Dockerfile.base
@@ -9,6 +9,7 @@
# 1. Define the ARG with a fallback default version
ARG FLUTTER_VERSION="3.44.3"
+ARG TARGETARCH
ENV TZ="America/Los_Angeles" \
DEBIAN_FRONTEND=noninteractive \
@@ -39,7 +40,12 @@
RUN set -ex; \
export PATH="/opt/flutter/bin:${PATH}"; \
# 1. Download and uncompress the flutter tool
- curl -fsSL https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_${FLUTTER_VERSION}-stable.tar.xz | tar -xJ --no-same-owner -C /opt; \
+ ARCH="${TARGETARCH:-$(dpkg --print-architecture)}"; \
+ EXCLUDE_CACHE=""; \
+ if [ "$ARCH" = "arm64" ] || [ "$ARCH" = "aarch64" ]; then \
+ EXCLUDE_CACHE="--exclude=flutter/bin/cache"; \
+ fi; \
+ curl -fsSL "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_${FLUTTER_VERSION}-stable.tar.xz" | tar -xJ --exclude=.pub-preload-cache $EXCLUDE_CACHE --no-same-owner -C /opt; \
# 2. Run config to ONLY ENABLE web (and disable everything else)
flutter config \
--enable-web \
diff --git a/cloud_build/flutter_agy_docker/README.md b/cloud_build/flutter_agy_docker/README.md
index 35ceaa1..47b9940 100644
--- a/cloud_build/flutter_agy_docker/README.md
+++ b/cloud_build/flutter_agy_docker/README.md
@@ -6,41 +6,25 @@
## 1. (Optional) Building the Docker Image Locally
-> **Important Platform Note:** Flutter does not fully support `linux/arm64` yet. If you are on an Apple Silicon (M1/M2/M3) machine, you *must* set the platform to `linux/amd64` when building and running.
+> **Platform Note:** This environment natively supports both `linux/amd64` (x86_64) and `linux/arm64` (Apple Silicon/ARM) architectures (for Dart/Flutter Web development only).
-### For x86 / Intel:
+### Building the Docker image:
```shell
podman build -t flutter_docker .
```
-### For Apple Silicon (using podman):
-```shell
-podman build --platform linux/amd64 -t flutter_docker .
-```
-
### Building a specific Flutter version:
You can override the default Flutter version using the `FLUTTER_VERSION` build argument.
```shell
-podman build --platform linux/amd64 --build-arg FLUTTER_VERSION=3.45.0 -t flutter_docker:3.45.0 .
+podman build --build-arg FLUTTER_VERSION=3.45.0 -t flutter_docker:3.45.0 .
```
## 2. Running the Docker Image Locally
You can run the container in any project folder. The current working directory (`$PWD`) will be mapped to `/app` inside the container.
-### For x86 / Intel:
```shell
podman run --userns=keep-id:uid=1000,gid=1000 -it \
- -v ".:/app:z" \
- -v "dart_tool_cache:/app/.dart_tool" \
- -v "dart_build_cache:/app/build" \
- -v "$USER/.gemini:/home/coder/.gemini:z" \
- flutter_docker:latest
-```
-
-### For Apple Silicon (using podman):
-```shell
-podman run --platform linux/amd64 --userns=keep-id:uid=1000,gid=1000 -it \
-v "${PWD}:/app:z" \
-v "dart_tool_cache:/app/.dart_tool" \
-v "dart_build_cache:/app/build" \
diff --git a/cloud_build/flutter_agy_docker/cloudbuild.yaml b/cloud_build/flutter_agy_docker/cloudbuild.yaml
index ba79450..6785012 100644
--- a/cloud_build/flutter_agy_docker/cloudbuild.yaml
+++ b/cloud_build/flutter_agy_docker/cloudbuild.yaml
@@ -4,52 +4,7 @@
steps:
# ==========================================
- # STEP 1: Build the Static Base Image
- # ==========================================
- - name: 'gcr.io/cloud-builders/docker'
- entrypoint: 'bash'
- args: ['-c', 'docker pull us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_base:latest || exit 0']
-
- - name: 'gcr.io/cloud-builders/docker'
- dir: 'cloud_build/flutter_agy_docker/'
- args:
- - 'build'
- - '--cache-from'
- - 'us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_base:latest'
- - '--build-arg'
- - 'FLUTTER_VERSION=$_FLUTTER_VERSION'
- - '-t'
- - 'us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_base:$_FLUTTER_VERSION'
- - '-f'
- - 'Dockerfile.base'
- - '.'
-
- # ==========================================
- # STEP 2: Build the Auto-Updating Dev Image
- # ==========================================
- - name: 'gcr.io/cloud-builders/docker'
- entrypoint: 'bash'
- args: ['-c', 'docker pull us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_docker:latest || exit 0']
-
- - name: 'gcr.io/cloud-builders/docker'
- dir: 'cloud_build/flutter_agy_docker/'
- args:
- - 'build'
- - '--cache-from'
- - 'us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_docker:latest'
- - '--build-arg'
- - 'FLUTTER_VERSION=$_FLUTTER_VERSION'
- # Force apt-get to run by injecting the unique build ID
- - '--build-arg'
- - 'CACHE_BUSTER=$BUILD_ID'
- - '-t'
- - 'us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_docker:$_FLUTTER_VERSION'
- - '-f'
- - 'Dockerfile'
- - '.'
-
- # ==========================================
- # STEP 3: Conditionally Tag and Push 'latest', 'stable', or 'beta'
+ # STEP 1: Determine tags based on version
# ==========================================
- name: 'gcr.io/cloud-builders/docker'
entrypoint: 'bash'
@@ -58,8 +13,7 @@
- |
set -euo pipefail
- # Install curl and jq (handles both Debian and Alpine builder variants)
- # We temporarily disable exit-on-error to allow the fallback to work cleanly
+ # Install curl and jq
set +e
apt-get update && apt-get install -y curl jq 2>/dev/null || apk add curl jq 2>/dev/null
set -e
@@ -79,16 +33,13 @@
BETA_VERSION=$$(echo "$$JSON_DATA" | \
jq -r '.current_release.beta as $$beta_hash | .releases[] | select(.hash == $$beta_hash) | .version' | head -n 1)
- # Validate that the versions were successfully parsed.
- # -z "$$STABLE_VERSION" checks if the variable is an empty string (e.g., if jq failed entirely).
- # "$$STABLE_VERSION" == "null" checks if jq succeeded but couldn't find the requested data in the JSON.
if [ -z "$$STABLE_VERSION" ] || [ "$$STABLE_VERSION" == "null" ]; then
- echo "Error: STABLE_VERSION could not be parsed from JSON. The JSON structure may have changed."
+ echo "Error: STABLE_VERSION could not be parsed."
exit 1
fi
if [ -z "$$BETA_VERSION" ] || [ "$$BETA_VERSION" == "null" ]; then
- echo "Error: BETA_VERSION could not be parsed from JSON. The JSON structure may have changed."
+ echo "Error: BETA_VERSION could not be parsed."
exit 1
fi
@@ -96,45 +47,103 @@
echo "Current beta version is: $$BETA_VERSION"
echo "Building for version: $_FLUTTER_VERSION"
+ BASE_TAGS="us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_base:$_FLUTTER_VERSION"
+ DEV_TAGS="us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_docker:$_FLUTTER_VERSION"
+
if [ "$_FLUTTER_VERSION" == "$$STABLE_VERSION" ]; then
- echo "Version $_FLUTTER_VERSION is stable. Tagging and pushing as latest and stable."
-
- # Tag the previously built versioned images
- docker tag us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_base:$_FLUTTER_VERSION us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_base:stable
- docker tag us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_base:$_FLUTTER_VERSION us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_base:latest
- docker tag us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_docker:$_FLUTTER_VERSION us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_docker:stable
- docker tag us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_docker:$_FLUTTER_VERSION us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_docker:latest
-
- # Push the new tags
- docker push us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_base:stable
- docker push us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_base:latest
- docker push us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_docker:stable
- docker push us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_docker:latest
-
+ echo "Version $_FLUTTER_VERSION is stable. Tagging as stable and latest."
+ BASE_TAGS="$$BASE_TAGS,us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_base:stable,us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_base:latest"
+ DEV_TAGS="$$DEV_TAGS,us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_docker:stable,us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_docker:latest"
elif [ "$_FLUTTER_VERSION" == "$$BETA_VERSION" ]; then
- echo "Version $_FLUTTER_VERSION is beta. Tagging and pushing as beta."
-
- # Tag the previously built versioned images
- docker tag us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_base:$_FLUTTER_VERSION us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_base:beta
- docker tag us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_docker:$_FLUTTER_VERSION us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_docker:beta
-
- # Push the new tags
- docker push us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_base:beta
- docker push us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_docker:beta
-
+ echo "Version $_FLUTTER_VERSION is beta. Tagging as beta."
+ BASE_TAGS="$$BASE_TAGS,us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_base:beta"
+ DEV_TAGS="$$DEV_TAGS,us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_docker:beta"
else
- echo "Version $_FLUTTER_VERSION is neither the stable nor beta release. Skipping extra tags."
+ echo "Version $_FLUTTER_VERSION is neither stable nor beta. Using version tag only."
fi
+ echo "BASE_TAGS=$$BASE_TAGS" > /workspace/tags.env
+ echo "DEV_TAGS=$$DEV_TAGS" >> /workspace/tags.env
+
+ # ==========================================
+ # STEP 2: Build the Multi-Arch Base Image
+ # ==========================================
+ - name: 'gcr.io/cloud-builders/docker'
+ dir: 'cloud_build/flutter_agy_docker/'
+ entrypoint: 'bash'
+ args:
+ - '-c'
+ - |
+ set -euo pipefail
+
+ # Register QEMU emulators for multi-arch build
+ docker run --privileged --rm tonistiigi/binfmt --install all
+
+ # Create a new buildx builder
+ docker buildx create --use
+
+ # Read tags
+ source /workspace/tags.env
+
+ # Convert comma-separated tags to -t arguments
+ IFS=',' read -ra ADDR <<< "$$BASE_TAGS"
+ TAG_ARGS=()
+ for tag in "$${ADDR[@]}"; do
+ TAG_ARGS+=("-t" "$$tag")
+ done
+
+ # Build and push the base image
+ docker buildx build \
+ --platform linux/amd64,linux/arm64 \
+ --cache-from us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_base:latest \
+ --cache-to type=inline \
+ --build-arg FLUTTER_VERSION=$_FLUTTER_VERSION \
+ "$${TAG_ARGS[@]}" \
+ -f Dockerfile.base \
+ --push \
+ .
+
+ # ==========================================
+ # STEP 3: Build the Multi-Arch Dev Image
+ # ==========================================
+ - name: 'gcr.io/cloud-builders/docker'
+ dir: 'cloud_build/flutter_agy_docker/'
+ entrypoint: 'bash'
+ args:
+ - '-c'
+ - |
+ set -euo pipefail
+
+ # Register QEMU emulators
+ docker run --privileged --rm tonistiigi/binfmt --install all
+
+ # Create a new buildx builder
+ docker buildx create --use
+
+ # Read tags
+ source /workspace/tags.env
+
+ # Convert comma-separated tags to -t arguments
+ IFS=',' read -ra ADDR <<< "$$DEV_TAGS"
+ TAG_ARGS=()
+ for tag in "$${ADDR[@]}"; do
+ TAG_ARGS+=("-t" "$$tag")
+ done
+
+ # Build and push the developer image
+ docker buildx build \
+ --platform linux/amd64,linux/arm64 \
+ --cache-from us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_docker:latest \
+ --cache-to type=inline \
+ --build-arg FLUTTER_VERSION=$_FLUTTER_VERSION \
+ --build-arg CACHE_BUSTER=$BUILD_ID \
+ "$${TAG_ARGS[@]}" \
+ -f Dockerfile \
+ --push \
+ .
+
substitutions:
_FLUTTER_VERSION: "3.44.3"
-# Push both the base and the final developer image to the registry
-images:
- - 'us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_base:$_FLUTTER_VERSION'
- - 'us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_docker:$_FLUTTER_VERSION'
-
-# Force Cloud Build to send logs directly to Cloud Logging
-# instead of an autogenerated GCS bucket
options:
logging: CLOUD_LOGGING_ONLY
diff --git a/cloud_build/flutter_agy_docker/docker-compose.yml b/cloud_build/flutter_agy_docker/docker-compose.yml
index 7db5d4e..a37e1f1 100644
--- a/cloud_build/flutter_agy_docker/docker-compose.yml
+++ b/cloud_build/flutter_agy_docker/docker-compose.yml
@@ -9,7 +9,6 @@
services:
dev:
image: us-docker.pkg.dev/flutter-infra/flutter-infra/flutter_docker:stable
- platform: linux/amd64
# Dynamically reads the environment variable
container_name: ${ENV_NAME:-coder-env}
stdin_open: true