Moving from Dockerfile to apko for Declarative Images

Moving from Dockerfile to apko for Declarative Images

Leader posted 5 min read

Instead of a Dockerfile, we write YAML-based files for apko. This file tells apko exactly which repositories to trust and which packages to materialise into the image.

mkdir apko-demo && cd apko-demo && cat <<EOF > wolfi.yaml
contents:
  repositories:
    - https://packages.wolfi.dev/os
  keyring:
    - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub
  packages:
    - wolfi-base
    - python-3.11
    - py3-pip

entrypoint:
  command: /usr/bin/python3

archs:
  - x86_64
  - aarch64
EOF

Why this is different from a Dockerfile:

  • repositories & keyring: You are explicitly defining the trust chain.
  • packages: These are pulled directly from Wolfi. There is no apt-get update.
  • archs: You can tell apko to build for both Intel and Apple Silicon at the same time.

You can simply run the below command to build the container image.
You don't even need the Docker daemon running for this part.

apko build wolfi.yaml my-wolfi-python:latest output.tar

So, what just happened? Well, apko resolved the dependency tree for Python 3.11. It downloaded the .apk files directly. It then proceeded to unpack them into a filesystem and wrapped them in an OCI-compliant .tar file. It then finished the process by generating an SBOM automatically in the same folder. You can then load and run the image with Docker, even though we never used Docker to build it, we can use Docker to run it.

docker load < output.tar
docker run --rm my-wolfi-python:latest-arm64 --version

Single-layer images are really, really cool

In a standard Dockerfile, every RUN, COPY, and ADD creates a new layer on disk. In apko, everything is flattened into a single, optimised layer. This makes pulling the image across a network much faster because the container runtime only has to fetch and extract one single blob.

docker history my-wolfi-python:latest-arm64

Look in your folder for a file ending in .sbom.json. Open it up. You should have a complete, machine-readable list of every single library inside your image, generated for free.

jq . sbom-aarch64.spdx.json
{
  "SPDXID": "SPDXRef-DOCUMENT",
  "name": "sbom-sha256:4df02c70193790be7e47ca9943cdce69959308bf13e5b095dc401cb0ae9d352f",
  "spdxVersion": "SPDX-2.3",
  "creationInfo": {
    "created": "2026-05-13T20:49:35Z",
    "creators": [
      "Tool: apko (1.2.12)",
      "Organization: Chainguard, Inc"
    ],
    "licenseListVersion": "3.27"
  },
  "dataLicense": "CC0-1.0",
  "documentNamespace": "https://spdx.org/spdxdocs/apko/",
  "documentDescribes": [
    "SPDXRef-Package-Image-sha256-7b4c80050663e9a70af6841bbf136a9e4af2d4f3338c1c2a462c8def09ef6cbf"
  ],
  "packages": [
    {
      "SPDXID": "SPDXRef-Package-Image-sha256-7b4c80050663e9a70af6841bbf136a9e4af2d4f3338c1c2a462c8def09ef6cbf",
      "name": "sha256:7b4c80050663e9a70af6841bbf136a9e4af2d4f3338c1c2a462c8def09ef6cbf",
      "versionInfo": "sha256:7b4c80050663e9a70af6841bbf136a9e4af2d4f3338c1c2a462c8def09ef6cbf",
      "filesAnalyzed": false,
      "description": "apko container image",
      "downloadLocation": "NOASSERTION",
      "supplier": "Organization: Wolfi",
      "primaryPackagePurpose": "CONTAINER",
      "checksums": [
        {
          "algorithm": "SHA256",
          "checksumValue": "7b4c80050663e9a70af6841bbf136a9e4af2d4f3338c1c2a462c8def09ef6cbf"
        }
      ],
      "externalRefs": [
        {
          "referenceCategory": "PACKAGE-MANAGER",
          "referenceLocator": "pkg:oci/my-wolfi-python@sha256:7b4c80050663e9a70af6841bbf136a9e4af2d4f3338c1c2a462c8def09ef6cbf?arch=arm64&mediaType=application%2Fvnd.oci.image.manifest.v1%2Bjson&os=linux",
          "referenceType": "purl"
        }
      ]
    },
    {
      "SPDXID": "SPDXRef-Package-ImageLayer-sha256-4df02c70193790be7e47ca9943cdce69959308bf13e5b095dc401cb0ae9d352f",
      "name": "sha256:4df02c70193790be7e47ca9943cdce69959308bf13e5b095dc401cb0ae9d352f",
      "versionInfo": "20230201",
      "filesAnalyzed": false,
      "description": "apko operating system layer",
      "downloadLocation": "NOASSERTION",
      "supplier": "Organization: Wolfi",
      "primaryPackagePurpose": "CONTAINER",
      "externalRefs": [
        {
          "referenceCategory": "PACKAGE-MANAGER",
          "referenceLocator": "pkg:oci/my-wolfi-python@sha256:4df02c70193790be7e47ca9943cdce69959308bf13e5b095dc401cb0ae9d352f?arch=arm64&mediaType=application%2Fvnd.oci.image.layer.v1.tar%2Bgzip&os=linux",
          "referenceType": "purl"
        }
      ]
    },
    {
      "SPDXID": "SPDXRef-OperatingSystem-wolfi",
      "name": "wolfi",
      "versionInfo": "20230201",
      "filesAnalyzed": false,
      "description": "Operating System",
      "downloadLocation": "NOASSERTION",
      "supplier": "Organization: Wolfi",
      "primaryPackagePurpose": "OPERATING_SYSTEM"
    },
    {
      "SPDXID": "SPDXRef-Package-apk-wolfi-baselayout-20230201-r29",
      "name": "wolfi-baselayout",
      "versionInfo": "20230201-r29",
      "filesAnalyzed": false,
      "licenseConcluded": "NOASSERTION",
      "licenseDeclared": "MIT",
      "downloadLocation": "NOASSERTION",
      "originator": "Organization: Wolfi",
      "supplier": "Organization: Wolfi",
      "copyrightText": "NOASSERTION",
      "primaryPackagePurpose": "APPLICATION",
      "externalRefs": [
        {
          "referenceCategory": "PACKAGE-MANAGER",
          "referenceLocator": "pkg:apk/wolfi/wolfi-baselayout@20230201-r29?arch=aarch64&distro=wolfi",
          "referenceType": "purl"
        }
      ]
    },
    {
      "SPDXID": "SPDXRef-Package-Melange-wolfi-baselayout.yaml-35e7b4713442869116936fae3fe8cf7c69bc9054",
      "name": "wolfi-baselayout.yaml",
      "versionInfo": "35e7b4713442869116936fae3fe8cf7c69bc9054",
      "filesAnalyzed": false,
      "licenseConcluded": "NOASSERTION",
      "licenseDeclared": "Apache-2.0",
      "downloadLocation": "NOASSERTION",
      "originator": "Organization: Wolfi",
      "supplier": "Organization: Wolfi",
      "primaryPackagePurpose": "INSTALL",
      "externalRefs": [
        {
          "referenceCategory": "PACKAGE-MANAGER",
          "referenceLocator": "pkg:github/chainguard-dev/stereo@35e7b4713442869116936fae3fe8cf7c69bc9054#wolfi-baselayout.yaml",
          "referenceType": "purl"
        }
      ]
    },

The above SBOM contains a smoking gun that proves exactly why the Wolfi ecosystem is more secure than standard Docker builds. While a normal SBOM (like one generated by scanning a Debian image) just says, "I think I found a package called X", this Wolfi SBOM includes a specific record that links the final binary back to the original source code instructions. Look at this exact package entry:

Melange is the secret sauce

"SPDXID": "SPDXRef-Package-Melange-wolfi-baselayout.yaml-35e7b4713442869116936fae3fe8cf7c69bc9054",
"name": "wolfi-baselayout.yaml",
"primaryPackagePurpose": "INSTALL",
"referenceLocator": "pkg:github/chainguard-dev/stereo@35e7b4713442869116936fae3fe8cf7c69bc9054#wolfi-baselayout.yaml"

This provides direct software provenance. Most SBOMs stop at the package-level. This SBOM goes a level deeper. It tells you exactly which Melange YAML file was used to build that specific .apk. It’s not just baselayout, it’s basically showing "baselayout as defined in this specific Git commit on GitHub".

In this scenario there’s no guesswork. Most SBOM tools (like Syft or Trivy) have to scan a finished image and guess what’s inside. Because apko builds the image by assembling packages it already knows about, this SBOM is authoritative. It was created during the build, not after. You can also see a reference to chainguard-dev/stereo. This is an internal-facing look at how Wolfi packages are organised. It shows that even the most basic parts of your OS (the baselayout) are treated like verifiable source code.

You’ll also notice the "licenseConcluded": "NOASSERTION" field in some spots. This isn't an error; it's actually a sign of a clean room build. It means apko is strictly reporting what was declared in the package metadata rather than trying to guess the license by regex-scanning files, which is how many security tools end up with false positives.

In short, this SBOM provides a clear chain of custody from your running container all the way back to a specific line of YAML in a GitHub repo. A standard Dockerfile can almost never provide that level of transparency - and that’s why I’m moving over to apko going forward.

Cleanup

As always, I leave a little cleanup script at the end of the exercise:

rm -r apko-demo
docker stop $(docker ps -q)
docker rm $(docker ps -aq)
docker rmi $(docker images -q)
docker images

More Posts

Dashboard Operasional Armada Rental Mobil dengan Python + FastAPI

Masbadar - Mar 12

I Wrote a Script to Fix Audible's Unreadable PDF Filenames

snapsynapseverified - Apr 20

Why I’m building my Images using Wolfi

Nigel Douglas - May 13

Optimizing the Clinical Interface: Data Management for Efficient Medical Outcomes

Huifer - Jan 26

The Roadmap: Moving from AI Chatbots to Autonomous Financial Agents

Pocket Portfolioverified - Mar 25
chevron_left

Related Jobs

View all jobs →

Commenters (This Week)

1 comment
1 comment
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!