@@ -249,9 +249,35 @@ pub fn build(force: bool, tag: Option<&str>, dockerfile: Option<&str>) -> Result
249249 // Write entrypoint
250250 std:: fs:: write ( build_path. join ( "entrypoint.sh" ) , ENTRYPOINT ) ?;
251251
252- // Copy crosslink binary
252+ // The Dockerfile COPYs `crosslink-${TARGETARCH}`, and the staged binary
253+ // must be a Linux binary to run in the agent image. `crosslink container
254+ // build` packages the *installed* binary (it has no source tree to
255+ // cross-compile from), so it can only produce a runnable image on a Linux
256+ // host of a supported arch. For cross-arch or non-Linux hosts, the CI
257+ // workflow (.github/workflows/container-image.yml) and `just build-image`
258+ // cross-compile a static musl binary instead.
259+ let docker_arch = match std:: env:: consts:: ARCH {
260+ "x86_64" => "amd64" ,
261+ "aarch64" => "arm64" ,
262+ other => bail ! (
263+ "unsupported host architecture `{other}` for `crosslink container build`; \
264+ build the image via CI (.github/workflows/container-image.yml) or `just build-image`"
265+ ) ,
266+ } ;
267+ if !cfg ! ( target_os = "linux" ) {
268+ bail ! (
269+ "`crosslink container build` packages the installed crosslink binary, which must \
270+ be a Linux binary to run in the agent image — but this host is `{}`. Build on a \
271+ Linux host, or use the CI workflow (.github/workflows/container-image.yml) or \
272+ `just build-image`, which cross-compile a static musl binary.",
273+ std:: env:: consts:: OS
274+ ) ;
275+ }
276+
277+ // Copy crosslink binary under the arch-suffixed name the Dockerfile expects.
253278 let binary = find_crosslink_binary ( ) ?;
254- std:: fs:: copy ( & binary, build_path. join ( "crosslink" ) )
279+ let staged_binary = format ! ( "crosslink-{docker_arch}" ) ;
280+ std:: fs:: copy ( & binary, build_path. join ( & staged_binary) )
255281 . context ( "Failed to copy crosslink binary to build context" ) ?;
256282
257283 // Compute binary hash for staleness detection
@@ -261,6 +287,9 @@ pub fn build(force: bool, tag: Option<&str>, dockerfile: Option<&str>) -> Result
261287
262288 let mut cmd = Command :: new ( "docker" ) ;
263289 cmd. args ( [ "build" , "-t" , & image] ) ;
290+ // Pin TARGETARCH so plain `docker build` (not buildx) resolves the COPY to
291+ // the arch-suffixed binary we staged, instead of the Dockerfile default.
292+ cmd. args ( [ "--build-arg" , & format ! ( "TARGETARCH={docker_arch}" ) ] ) ;
264293 cmd. args ( [ "--label" , LABEL_AGENT ] ) ;
265294 cmd. args ( [ "--label" , & format ! ( "crosslink-binary-hash={binary_hash}" ) ] ) ;
266295 if force {
0 commit comments