For the complete documentation index, see llms.txt. This page is also available as Markdown.

Sealing OS packages in a Dockerfile

Insert `seal fix --os` into a Dockerfile so the resulting image ships with sealed OS packages.

The canonical place to run seal fix --os is inside a Dockerfile RUN step, after the base image's package manager has installed the OS packages your application needs and before the build does anything that drops privileges or finalizes the image.

The CLI auto-detects the distribution and dispatches to the matching OS-package family. One pattern covers Alpine, Debian / Ubuntu, and the Red Hat family alike; the only thing that changes is which base image you started from.

Pattern

# Whatever base image your application uses.
FROM alpine:3.20

# Install the OS packages your application needs, normally.
RUN apk add --no-cache curl ca-certificates openssl

# Drop the Seal CLI into the image and seal the OS packages.
ADD --chmod=755 https://github.com/seal-community/cli/releases/download/latest/seal-linux-amd64-latest /usr/local/bin/seal
ARG SEAL_TOKEN
ARG SEAL_PROJECT=my-project-id
RUN seal fix --os --mode all --remove-cli

# Continue the build with the sealed OS packages in place.
COPY . /app
WORKDIR /app
CMD ["/app/server"]

What the relevant lines do:

  1. ADD --chmod=755 ... /usr/local/bin/seal downloads the latest CLI binary into the image and marks it executable.

  2. ARG SEAL_TOKEN and ARG SEAL_PROJECT accept the credentials from the docker build invocation. The variables are available only during the build; they do not persist into the final image.

  3. RUN seal fix --os --mode all detects the distribution, identifies which installed OS packages have sealed versions available, and replaces them in place.

  4. --remove-cli deletes the CLI binary from the image after the run so it does not ship in the final image. Drop the flag if you want to keep the CLI available at runtime (for example, to run scans against the running container).

Passing credentials from the build

Pass SEAL_TOKEN from your CI's secret store as a build argument:

In CI, the host-side SEAL_TOKEN is your CI's secret-store-backed environment variable. --build-arg propagates it into the Dockerfile's RUN seal fix --os step without writing it to a layer.

For BuildKit-enabled builds, the more secure form uses build secrets instead of --build-arg. The build argument form above is the simpler default; the BuildKit form is documented in the CI integration page.

Per-distribution notes

The CLI handles the distribution-specific package manager work for you. There are still a few base-image conventions worth respecting in the surrounding Dockerfile:

  • apk add --no-cache is the idiomatic install line; the CLI works against the resulting package database.

  • Alpine images are small; the CLI's network round-trips to apk.sealsecurity.io are the dominant cost of the seal step.

  • The CLI updates the apt source list itself when it needs to pull from deb.sealsecurity.io. You do not need to add a Seal entry to /etc/apt/sources.list by hand.

  • Keep the install + clean-up on one layer (apt-get update && apt-get install && rm -rf /var/lib/apt/lists/*) the way you normally would; the seal step runs after that layer is committed.

  • Works with dnf (modern RHEL family) and yum (older RHEL family) alike. The CLI detects which one is present.

  • On Amazon Linux 2 use RUN yum install ... instead of dnf. The CLI behavior is the same.

Multi-stage builds

In a multi-stage build, run seal fix --os in the final stage, the one whose layers ship in the published image. Sealing the OS packages of a builder stage that is later discarded is wasted work and does not protect the runtime image.

Layer ordering and caching

docker build caches each RUN step's result by hashing the step's inputs. The seal fix --os step's output depends on the sealed-package set Seal currently publishes for your distribution, which Docker has no way to know about: from Docker's perspective, the step's inputs (the previous layer, the command string, the ARG values) might look identical between two builds even when Seal has published a new sealed iteration.

Two ways to manage that:

  • Pin to specific sealed iterations. Use --mode remote or --mode local with explicit Sealing Rules. Bumping a rule changes the rule contents the CLI reads at build time, which propagates into the layer cache invalidation chain.

  • Bust the cache periodically. Add a no-op ARG like ARG SEAL_REBUILD_TOKEN=1 and pass --build-arg SEAL_REBUILD_TOKEN=$(date +%Y%m%d) from CI so every build at least once a day re-runs the seal step against the latest sealed iterations.

The right approach depends on whether your team's mental model for "what's in this image" is pinned (rules) or rolling (latest safest).

Verify

After the build finishes, run the produced image and ask the OS package manager for the version of any sealed package:

The reported version differs from the upstream origin version in a way that identifies the sealed iteration. The exact form is Alpine-specific and documented in Seal's naming and versioning conventions.

The version field carries the sealed-iteration suffix (+spN).

The package release field carries the sealed-iteration suffix (+spN).

The exact version-string format per OS-package family is documented in Seal's naming and versioning conventions reference.

Common pitfalls

  • The CLI runs before all apk add / apt-get install / dnf install steps. The packages installed after the seal step ship in their upstream vulnerable form. Place seal fix --os after every step that installs or upgrades OS packages so the full installed set is sealed.

  • The image drops to a non-root user before the CLI runs. The seal step needs to write to the package-manager database. Run seal fix --os while the build is still USER root, then drop privileges with a later USER directive.

  • The base image is one Seal does not support. The CLI exits non-zero with a message naming the detected distribution. The supported distributions are the apk, deb, and rpm families enumerated in the seal fix --os reference.

Last updated