Containers that exit immediately with exec format error invite the wrong instinct: add a capability, mount a device, edit the tunnel configuration. None of those reaches the problem. The kernel could not execute the binary at all, which in a container almost always means the image targets a different CPU architecture from the host. Everything you might change about routing, credentials or privileges happens after a program has started, and no program started.

The startup message that looks like a configuration error

An exec format failure comes from the loader, before the application reads a single setting. That is why it produces no configuration warnings and no tunnel log, because there is no process alive to write them. The failure is also remarkably fast, often before any container log line appears at all. The same short message appears whether the binary is for another architecture, for another operating system or damaged.

Record the runtime environment and architecture alongside the exact image reference, then check what that reference actually offers. On Docker Desktop the container platform belongs to the Linux virtual machine rather than to the laptop, so the host architecture on its own can mislead. Read the index entry list for this reference instead of the marketing text on its tag page.

Read the image index before the run command

When a registry publishes a multi-platform reference, the tag points at an index, and each entry names a platform with its own digest. Inspect the reference under review rather than a neighbour tag, and read the index instead of assuming what it contains.

docker buildx imagetools inspect ghcr.io/example/vpn:1.4
docker image inspect --format '{{.Os}}/{{.Architecture}}' ghcr.io/example/vpn:1.4

The first command lists the platforms the reference publishes. The second reports the platform of the image already present locally, which is what the runtime will run. A reference with only one entry means every host receives the same binary, and the tag cannot help a machine it was never built for.

Labels select a variant; they do not create one

A multi-platform index lets the runtime pick a matching entry, which is genuinely convenient and frequently mistaken for portability. The selection is a lookup, not a translation: if the variant is absent, there is nothing to select. The same tag on two hosts can therefore resolve to two different binaries, and on one host it resolves to nothing usable.

Pinning by digest removes the ambiguity, and it also makes a later review possible, because the digest identifies exactly the bytes that were tested. Keep that digest with the deployment record instead of relying on the tag that happened to be current.

Emulation is an arrangement, not a translation layer

Emulation through a supported runtime can run some non-native user-space workloads, so the absolute claim that one architecture can never run another's image is wrong. It is also not a general fix. Emulated execution has different performance characteristics, and a trivial process that starts tells you nothing about whether a tunnel workload behaves acceptably.

Docker's multi-platform guide treats native builds, cross-compilation and emulation as separate strategies, which is the right way to read the options. Choose one deliberately, and qualify the VPN workload under it rather than assuming equivalence.

Permissions solve a different problem

Granting a network-administration capability or passing through a TUN device cannot make an incompatible executable run. These are runtime permissions that a running process uses; they are invisible to a loader that has already refused the file.

The reverse is equally true. A native binary proves nothing about privileges, device access, resolver integration or the restart policy a tunnel needs, so a container that starts is not yet a container that works. Establish the platform contract first, then move to the runtime contract with its documented options.

Keep the image digest, the supported target and the configuration together in the same record. A tag update that silently changes architecture is easier to catch when the previous digest is written down than when the only symptom is a familiar error and a fresh afternoon of debugging.

مۇناسىۋەتلىك ماقالىلەر