System designHow-to

Docker builds for arm64: fix find_package dependencies

Why a camera SDK Docker build for arm64 stops at find_package: pull the arm64 image tag and install the glog and gflags dependencies the config expects.

LumaSpec Vision9 min read Share on LinkedIn

On this page 9 sections
  1. 01What you need
  2. 02Why the arm64 build fails on a library you never call
  3. 03Fix it in six steps
  4. 04A minimal Dockerfile that builds clean
  5. 05Worked example: what emulated builds cost you
  6. 06Emulated build, native arm64 builder, or cross toolchain
  7. 07Checks before you ship
  8. 08When arm64 is the wrong answer
  9. 09Questions

Your camera client compiles without complaint on the amd64 workstation, the identical Dockerfile aimed at linux/arm64 dies inside CMake, and the missing library — glog or gflags — is one your own code never calls. That combination points at three specific causes: the base image you actually pulled is the wrong architecture, the arm64 image ships without the development packages CMake's config files demand, and boilerplate installs such as tzdata abort the layer before apt finishes. This walkthrough gives you the exact FROM line, the exact apt package list and a way to prove the finished image is arm64 before it goes near the device.

What you need

A Docker installation with BuildKit: Docker Desktop ships buildx, and on a Linux server you add the buildx plugin once. Bring the camera SDK's base image name and its tag list from the SDK vendor's registry page — the per-architecture tags live there. Keep your CMakeLists.txt untouched; in the common case the fix lives entirely in the Dockerfile. Budget fifteen minutes for the first passing build, and expect each later emulated rebuild of a small client to take minutes rather than hours.

The failing configure usually stops inside lines like these, wrapped in your own fatal-error guard:

find_package(glog CONFIG REQUIRED)
find_package(camera-sdk CONFIG REQUIRED)

glog appears here though your code never calls it. Where that requirement comes from — and why installing gflags by hand sends you down a rabbit hole — is the next section.

Why the arm64 build fails on a library you never call

CONFIG-mode find_package does not read your code; it executes the SDK's config file, a camera-sdk-config.cmake under /usr/lib/cmake/. That file declares glog as a link dependency of the components you requested, and glog's own config then requires gflags. You are satisfying the SDK's transitive dependencies, not your own — the full diagnosis method is in CMake missing dependency diagnostics for vision builds.

Whether that chain resolves is decided by three causes, in order of probability:

  1. Wrong architecture in FROM. A base image tag without an architecture suffix resolves to the build host's architecture, so on your amd64 machine FROM camera-sdk-base:stable pulled amd64 layers all along. The arm64 variant ships under its own tag, in the pattern version-ubuntu-arm64 — for example v1.1.1-ubuntu-arm64.
  2. Missing dev packages. The image carries the runtime library libgoogle-glog0v5 but not libgoogle-glog-dev, and find_package needs the headers and CMake config that only the -dev package installs.
  3. Boilerplate layers that abort. tzdata runs an interactive timezone dialog in its setup script; a build step has no terminal to answer it, so the layer hangs and dies mid-install.

Fix it in six steps

  1. Confirm the target. Run uname -m on the runtime box: aarch64 means arm64 and you continue; x86_64 means amd64, and you should stop and build the plain image (the last section covers that case).
  2. Pin the architecture in FROM. Use the arm64 tag if the SDK ships one — FROM camera-sdk-base:v1.1.1-ubuntu-arm64 — or force the platform with FROM --platform=linux/arm64 camera-sdk-base:stable. Prefer the explicit arch tag: it reads unambiguously in logs and works on builders without emulation handlers.
  3. Make apt unattended and lean. Keep ARG DEBIAN_FRONTEND=noninteractive. Drop tzdata unless the application reads zone data, and drop apt-utils, iputils-ping, net-tools and iproute2 unless the app uses them — each is another dependency chain to resolve on a foreign architecture.
  4. Install the toolchain and the glog pair in one layer: build-essential, cmake, coreutils, libgoogle-glog0v5, libgoogle-glog-dev. These five proved sufficient in the minimal working image: glog's config finds gflags among their dependencies. Add libgflags-dev only if configure still reports gflags missing.
  5. Configure and build clean. Run cmake -S . -B build && cmake --build build from an empty build directory, with your two find_package lines unchanged.
  6. Prove the result. Build once with docker build --no-cache for an honest trace, then run docker image inspect on the output tag and read the Architecture field: it must say arm64.

A minimal Dockerfile that builds clean

The image, not your workstation, defines the toolchain here — the same property that makes Docker a reproducible firmware build environment for embedded targets. That is the whole reason to fix the Dockerfile rather than the CMake files:

FROM camera-sdk-base:v1.1.1-ubuntu-arm64
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
 && apt-get install -y --no-install-recommends \
      build-essential cmake coreutils \
      libgoogle-glog0v5 libgoogle-glog-dev \
 && rm -rf /var/lib/apt/lists/*
WORKDIR /client
COPY CMakeLists.txt ./
COPY src ./src
RUN cmake -S . -B build && cmake --build build

Three details earn their lines. --no-install-recommends keeps the layer small and skips packages you never asked for. Run apt as root, and reach for sudo only if the base image logs you in as a non-root user. Put your ENTRYPOINT or CMD after the build layer, so a failed compile fails the build instead of shipping a half-made image.

When the image builds but the client will not start on the device, the problem has moved to runtime: running 3D camera control software in Docker covers startup failures, and passing V4L2 media nodes to a camera in Docker covers device access inside the container.

Worked example: what emulated builds cost you

An amd64 host executes arm64 RUN steps under QEMU user emulation, and instruction translation makes compile-heavy steps run about 5-10× slower than native on the same machine — the standing rule of thumb for mixed-architecture builders.

Take a small client that configures in 60 s and compiles in 240 s natively: 300 s native in total. Emulated, the same steps take 300 × 5 = 1,500 s ≈ 25 min at the good end and 300 × 10 = 3,000 s = 50 min at the bad end. Rebuilding five times a day during integration then costs 5 × 25 to 50 min = 125-250 min — 2 to 4 h per day, or roughly 20-40 h across a ten-working-day sprint.

The decision follows directly from that arithmetic: emulate while a clean native build stays under about 10 minutes; beyond that, put native arm64 silicon on the network, because it repays the setup inside the first week.

Emulated build, native arm64 builder, or cross toolchain

Approach Set-up Build speed Choose it when
buildx + QEMU on your amd64 host Docker only 5-10× slower than native small clients, occasional images
Native arm64 builder one arm64 machine registered with buildx native speed daily rebuilds during a sprint
Cross toolchain CMAKE_TOOLCHAIN_FILE plus a maintained arm64 sysroot fast, no emulation large codebases on stable SDK versions

Start emulated: it proves the Dockerfile with no extra hardware. Move to a native arm64 builder when the sprint math above starts to bite. The cross toolchain is fastest, but you re-create the SDK's library set yourself — the maintenance the base image was saving you.

Checks before you ship

  • Manifest preflight. docker manifest inspect camera-sdk-base:v1.1.1-ubuntu-arm64 lists every platform the tag covers; confirm linux/arm64 appears before pulling.
  • Architecture after build. docker image inspect camera-client:arm64 must report arm64. If it reports amd64, the FROM line pulled the host architecture and every later layer inherited it.
  • One frame on real hardware. Build for the device and acquire once; getting frames on arm64 has its own pitfalls beyond the build, and fixing the arm64 acquisition binary layer is the checklist for that stage.
  • Runtime libraries present. A startup failure that names libglog.so.0 as a missing shared object means the image that runs the client is not the image you built — check which tag the deployment pulled. Diagnosing missing shared libraries on Linux walks that error down step by step.
  • One clean build on the CI runner. Run a single --no-cache build where the image will be produced, so a stale amd64 cache layer cannot hide.

When arm64 is the wrong answer

The runtime box is x86-64. Check the processor line before building anything: the vision controllers in the shop run Intel processors, so images for them are plain amd64 and this fix is unnecessary. Arm64 enters the picture with arm64 boards and edge devices — the case this article treats.

You never needed the SDK's find_package. If you link the SDK's prebuilt libraries by path instead, the glog requirement can disappear, but the version matching becomes yours and the failure moves to link time; diagnosing CMake linking dependencies covers that stage.

The codebase is large and rebuilt daily. Skip emulation and go straight to a native arm64 builder; the worked example above is the budget that justifies the hardware.

So the decision is one line per case: aarch64 target, six steps above; x86_64 target, plain amd64 image; daily rebuilds, native silicon. If a build still fails after the six steps, send the full CMake trace plus your FROM line through the contact page — the FROM line usually names the problem outright. More build-side guides sit on the systems topic page.

Questions

Do I need buildx and QEMU to build an arm64 image on an amd64 host?

Both, in separate roles. Buildx selects the target platform and drives the build; QEMU user emulation executes the arm64 instructions inside RUN steps. Docker Desktop ships both. On a Linux server, install the QEMU static binaries and register them with binfmt_misc once, or every arm64 RUN step fails with an exec format error. The build then runs unchanged.

Why does find_package demand glog when my code never calls it?

CONFIG-mode find_package executes the SDK's own config file, which declares glog as a dependency of the components you request; glog's config then requires gflags. The requirement is transitive, so it appears no matter how clean your CMakeLists is. Installing the glog dev package satisfies the whole chain without touching your files.

Which Ubuntu packages provide glog and gflags on arm64?

libgoogle-glog0v5 is the runtime shared library and libgoogle-glog-dev carries the headers plus the CMake config that find_package(glog CONFIG) loads. libgflags-dev covers gflags the same way. The package names are identical on amd64 and arm64: apt reads the architecture from the base image, so one install list serves both builds. On current Ubuntu the glog dev package also pulls its gflags counterpart as a dependency.

Why does apt fail to locate a package that exists on my workstation?

Layer hygiene explains it. Package indexes live in /var/lib/apt/lists, and a Dockerfile that ends one RUN layer by deleting that directory leaves the next RUN with nothing to install from. Pair apt-get update and apt-get install in the same RUN layer, and after any failed build run update again before concluding that a package name is wrong.

Can I keep tzdata in the image?

Yes, provided apt never sees a prompt. tzdata opens an interactive timezone dialog in its setup script; a docker build has no terminal, so the layer hangs until the builder kills it. With ARG DEBIAN_FRONTEND=noninteractive set before that layer, tzdata installs unattended. If the runtime application never reads zone data, dropping the package keeps the layer smaller instead.