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.
On this page
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.shInstalled /usr/local/bin/caddy from docker.io/library/caddy:latestpodman 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.