Skip to content
Search Fedora

    Browse this documentation

    Containerfile and multi-stage builds with Podman on Fedora

    No Docker is needed to build images on Fedora: podman build reads a Containerfile (Dockerfile syntax is fully compatible).

    Terminal window
    podman --version
    dnf list --available buildah | tail -1
    podman version 6.1.0
    buildah.x86_64 2:1.45.1-1.fc45 fedora

    Multi-stage build separating builder and runtime, final image 11.1 MB

    Both names work and the syntax is identical. Podman looks for Containerfile first, then Dockerfile.

    Terminal window
    podman build -t myapp:1 .
    podman build -f Containerfile -t myapp:1 .

    Convention: Containerfile for Podman/Buildah, Dockerfile for Docker. Mixing them breaks nothing.

    Compiled languages (Go/Rust/Java) need large build dependencies that the runtime does not. Multi-stage builds separate the build environment from the runtime so only the binary ships.

    Measured with a small Go program:

    Terminal window
    mkdir -p /tmp/cf && cd /tmp/cf
    cat > Containerfile <<'EOF'
    FROM docker.io/library/golang:1.27-alpine AS build
    WORKDIR /src
    RUN echo 'package main' > main.go && echo 'import "fmt"' >> main.go && echo 'func main(){ fmt.Println("built in stage 1") }' >> main.go
    RUN go build -o /out/hello main.go
    FROM docker.io/library/alpine:latest
    COPY --from=build /out/hello /usr/local/bin/hello
    CMD ["hello"]
    EOF
    podman build -t demo:1 .
    Terminal window
    podman images --format "{{.Repository}}:{{.Tag}} {{.Size}}" | grep demo
    podman run --rm demo:1
    localhost/demo:1 11.1 MB
    built in stage 1

    The final image is 11.1 MB. A single-stage build would carry the whole Go toolchain — hundreds of megabytes.

    ApproachFinal sizeNote
    single stage (golang base)hundreds of MBcompiler and sources stay in the image
    multi-stage (measured)11.1 MBonly the binary and the alpine base

    COPY --from=build is the key: it pulls files from the previous stage, which itself never enters the final image.

    Every RUN/COPY is a layer, and layer order decides cache hits:

    # bad: a one-line code change reinstalls every dependency
    COPY . /app
    RUN npm install
    # good: install dependencies first, copy code last
    COPY package.json package-lock.json /app/
    RUN npm install
    COPY . /app

    The effect is visible:

    Terminal window
    podman build -t demo:1 . # first build: everything
    podman build -t demo:2 . # second build: every layer cached
    STEP 1/3: FROM ...
    --> Using cache

    --> Using cache marks a hit. After a code change only the layers after COPY . /app rebuild.

    ToolGood for
    podman buildeveryday builds
    buildahscripting, fine-grained work without a daemon, stepwise CI builds
    podman buildxmulti-architecture builds
    Terminal window
    sudo dnf install buildah
    buildah --version

    buildah can build without any Containerfile:

    Terminal window
    ctr=$(buildah from alpine)
    buildah run $ctr -- apk add --no-cache curl
    buildah commit $ctr mycurl:1
    buildah rm $ctr
    Terminal window
    podman run -d --name registry -p 5000:5000 docker.io/library/registry:2
    curl -s http://127.0.0.1:5000/v2/_catalog

    Tag and push:

    Terminal window
    podman tag demo:1 127.0.0.1:5000/demo:1
    podman push 127.0.0.1:5000/demo:1
    curl -s http://127.0.0.1:5000/v2/_catalog
    {"repositories":["demo"]}
    SymptomCause
    base image pull times outno registry mirror; see domestic mirrors
    enormous imagesingle-stage build kept the toolchain
    code changed but the build did notan unintended cache hit; check COPY order or use --no-cache
    COPY --from=build cannot find the stagethe AS build name must match exactly
    push to port 5000 failsregistry not running, or SELinux/firewall blocking the port
    multi-arch build failsqemu-user-static and podman build --platform are required