Build Intel64-compatible Docker images from Mac M1

I was building a tracking pixel API app for internal websites. A tracking pixel is an HTML code snippet loaded when a user visits a website. I built this app on my laptop, MAC M1, and I run my Docker container locally within Docker Dekstop.

All was good when I ran that app locally on my laptop, and I could do all the coding and testing of the app. Rolling out the App to the end user for user testing on my laptop would not be sufficient. I decide to host my app on Google Cloud Run.

My tracking pixel app is built with the Python Flask framework; the pixel app uses Google Firestore to store the count of a web page view where the iframe with URL that points to the Pixel App is installed. If you are interested, you can contribute to the app or use it as you like, code is available on GitHub.

Deploying to Cloud Run

Building the docker image and pushing it to Google Cloud Registry was straightforward. These are my docker commands for building and pushing.

$ docker build -t eu.gcr.io/<project-id>/pixelcount .
$ docker push eu.gcr.io/<project-id>/pixelcount

The <project-id> is your Google Cloud Project Id; as my work is in the Europe zone, I use eu.gcr.io as my Container Registry location.

When I tried to deploy it to Cloud Run, I encountered a problem. By the way, I use Terraform for deploying to Cloud Run. Here is the code of main.tf

# main.tf
terraform {
  required_version = ">= 0.14"

  required_providers {
    # Cloud Run support was added on 3.3.0
    google = ">= 3.3"
  }
}
# Configure GCP project
provider "google" {
  credentials = file("../key.json")
  project     = var.gcp_project_id
  region      = "europe-north1"
}

resource "google_project_service" "cloud_run" {
  service = "iam.googleapis.com"
  disable_dependent_services = true
  disable_on_destroy = false
}

data "google_container_registry_image" "pixelcount" {
  name = var.image_name
}

# Deploy image to Cloud Run
resource "google_cloud_run_service" "pixelcount" {
  depends_on = [
    google_project_service.cloud_run
  ]

  name     = "pixelcount"
  location = var.gcp_region

  template {
    spec {
      containers {
        image = "eu.gcr.io/<projectid>/pixelcount"
        ports {
          container_port = 8080
        }
      }
    }
  }
  traffic {
    percent         = 100
    latest_revision = true
  }
}

# Create public access
data "google_iam_policy" "all_users_policy" {
  binding {
    role    = "roles/run.invoker"
    members = ["allUsers"]
  }
}

# Enable public access on Cloud Run service
resource "google_cloud_run_service_iam_policy" "all_users_iam_policy" {
  location    = google_cloud_run_service.pixelcount.location
  project     = google_cloud_run_service.pixelcount.project
  service     = google_cloud_run_service.pixelcount.name
  policy_data = data.google_iam_policy.all_users_policy.policy_data
}

Every time I run the terraform code with:

terraform plan
terraform apply

I got an error deploying from my local machine

“Error waiting to create Service: resource is in failed state “Ready:False”, message: Cloud Run error: The user-provided container failed to start and listen on the port defined provided by the PORT=8080 environment variable. Logs for this revision might contain more information.”

I also went through the Google Cloud Run logs, and the message was clear:

Error detected in pixelcount version pixelcount-00001-xuk

terminated: Application failed to start: Failed to create init process: failed to load /usr/local/bin/python: exec format error

Solution

I searched and searched for what could cause this. I tried different ways – deleting the Container Registry entry, tried to use a different Container Registry, gcr.io and us.gcr.io, eventually, I remembered that I was working on another Python project some time ago, and I had a problem installing a Python module with – pip install <name>. At that time, the problem was associated with the Apple M1. The python module I wanted to install had not yet been available for Apple M1.

After remembering that issue, I tweaked my search. I found a different set of search results that led me to this entry on Stackoverflow, which suggested that I should use docker buildx build --platform linux/amd64 it because I have been building my Docker image for Mac M1 ARM. Google Cloud Platform requires a Linux AMD docker image.

This is because the docker images built with Apple Silicon (or another ARM64-based architecture) can create issues when deploying the images to a Linux or Windows-based AMD64 environment (e.g. Google Cloud Run etc.) For example, you may try uploading your docker image made on the M1 chip to a Google Cloud Container Registry repository, and it fails to run. Therefore, you need a way to build AMD64-based images on the ARM64 architecture, whether using Docker build (for individual images) or docker-compose build (e.g. for multi-image apps running in a docker-compose network).

I followed the advice suggested in the Stackoverflow post and use docker buildx  that is the docker command I used in the end.

docker buildx build \
--platform linux/amd64 \
--push \
-t eu.gcr.io/<project-id>/pixelcount .

That built and pushed the docker image to Container Registry. After that, I executed terraform plan & apply that and deployed my docker image to Cloud Run without any errors. That was my journey in solving a problem that occurs from using different Chipsets. Just something I have to keep in mind in my future work.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *