Skip to content
Skip to the article
In Containers: 4 articles
Containers

Install a Binary from a Container Image

Copy a program and its default config out of a Docker or Podman image to install it directly on the host without running a container.

Updated
Applies to
  • Podman 5
  • Docker Engine 27+
  • Ubuntu 24.04
Tags
  • podman
  • docker
  • containers
  • packaging
Reading time
3 min

Some projects publish their software only as a container image. When you'd rather run the program directly on the host (under systemd, for example), you can create a container from the image without starting it and copy the files out. This works best for statically linked binaries, such as most Go programs.

Check What the Binary Needs

Find the binary's path in the image and check whether it's statically linked. A dynamically linked binary needs the same shared libraries (and the same C library, glibc or musl) on the host:

podman run --rm --entrypoint sh <IMAGE> -c 'command -v <PROGRAM>'
podman create --name inspect-tmp <IMAGE>
podman cp inspect-tmp:/usr/bin/<PROGRAM> /tmp/<PROGRAM>
podman rm inspect-tmp
file /tmp/<PROGRAM>
ldd /tmp/<PROGRAM>

file reports statically linked for self-contained binaries. If ldd lists libraries that the host doesn't have, install the matching distribution packages, or keep running the program as a container.

Install Script

This script copies one binary, and optionally a default config file, from an image onto the host. It never starts the container, and it removes the temporary container even if a step fails. It works with Docker too: set ENGINE=docker.

#!/usr/bin/env bash
# Copy a binary (and optionally its default config) out of a container image.
set -euo pipefail

IMAGE="${IMAGE:?set IMAGE, e.g. docker.io/library/caddy:latest}"
BINARY="${BINARY:?set BINARY, the path inside the image, e.g. /usr/bin/caddy}"
DEST="${DEST:-/usr/local/bin}"
CONFIG_SRC="${CONFIG_SRC:-}"
CONFIG_DEST="${CONFIG_DEST:-}"
ENGINE="${ENGINE:-podman}"

container=$("$ENGINE" create "$IMAGE")
trap '"$ENGINE" rm -f "$container" >/dev/null' EXIT

"$ENGINE" cp "$container:$BINARY" "$DEST/$(basename "$BINARY")"
chmod 0755 "$DEST/$(basename "$BINARY")"

# Copy the default config only if there isn't one already.
if [[ -n "$CONFIG_SRC" && -n "$CONFIG_DEST" && ! -e "$CONFIG_DEST" ]]; then
  mkdir -p "$(dirname "$CONFIG_DEST")"
  "$ENGINE" cp "$container:$CONFIG_SRC" "$CONFIG_DEST"
fi

echo "Installed $DEST/$(basename "$BINARY") from $IMAGE"

Save it as install-from-image.sh and run it as root:

IMAGE=docker.io/library/caddy:latest \
BINARY=/usr/bin/caddy \
CONFIG_SRC=/etc/caddy/Caddyfile \
CONFIG_DEST=/etc/caddy/Caddyfile \
./install-from-image.sh
Installed /usr/local/bin/caddy from docker.io/library/caddy:latest

podman create pulls the image if it isn't present locally. Installing to /usr/local/bin keeps the binary out of paths managed by the package manager.

After Installing

  • Create any runtime or state directories the program expects (check the image's documentation or podman image inspect <IMAGE> for volumes and environment variables). For example: mkdir -p /run/<PROGRAM> /etc/<PROGRAM>.

  • Write a systemd unit to run it as a service.

  • Updates aren't automatic. Re-run the script after pulling a newer image tag, then restart the service.

Tip

To copy an entire directory tree rather than one file, podman cp works on directories too. Exporting the whole filesystem with podman export <CONTAINER> | tar -x -C <DIR> is rarely needed.

Sources

This article is in the public domain (CC0 1.0), code samples included. Use it however helps you.