> For the complete documentation index, see [llms.txt](https://docs.sealsecurity.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.sealsecurity.io/setup-containers/seal-base-images/mirroring.md).

# Mirroring to your internal registry

Pulling Seal Base Images directly from Seal's hosted registry is the simplest path and the right default. There are cases where mirroring them into your own internal registry is the right call instead:

* **Policy requires that all production images come from a single internal registry.** Many organizations restrict cluster nodes and CI agents to pull only from the company's own registry, both for auditability and to keep the cluster's blast radius narrow.
* **Air-gapped environments** with no public-internet access. The internal registry is the only registry available.
* **Egress-cost or rate-limit pressure.** A large fleet that re-pulls the same sealed image across many nodes saves egress and avoids registry rate limits by pulling once into the internal mirror and then serving the rest of the fleet locally.
* **You already mirror upstream images.** If your team already runs an internal mirror for `python`, `node`, and friends, treating Seal Base Images the same way fits the existing operational pattern.

The mirror is structural: the same image, repushed under a different registry hostname. The image content, signature, attestations, and labels are unchanged.

## The mechanics

OCI registries support push-pull mirroring through standard tooling. The two common approaches:

* **`crane` or `skopeo` for one-shot copies.** Both tools copy a single image (or all tags of a repository) between registries with no intermediate `docker pull`. Suitable for ad-hoc syncs and scripted batch syncs.
* **A registry mirror agent for continuous sync.** Harbor, JFrog Artifactory, Sonatype Nexus, AWS ECR Pull Through Cache, and similar registry products have built-in or configurable mirror modes that replicate from an upstream registry on a schedule or on first pull. Suitable for ongoing operational mirroring at scale.

Pick the one that fits your existing operational pattern; the rest of this page covers the one-shot pattern in detail.

## One-shot copy with `crane`

[`crane`](https://github.com/google/go-containerregistry/blob/main/cmd/crane/doc/crane.md) is a minimal OCI client well suited to scripted copies.

```bash
# Authenticate against both registries (one-time setup).
crane auth login <seal-registry> -u <seal-username> -p <seal-password>
crane auth login <internal-registry> -u <internal-username> -p <internal-password>

# Copy the sealed image from Seal's registry to your internal registry.
crane copy \
  <seal-registry>/python:3.12.9-slim-bullseye-20250408T180700Z-sp2 \
  <internal-registry>/sealed/python:3.12.9-slim-bullseye-20250408T180700Z-sp2
```

`crane copy` preserves the manifest list, the SBOM, and the cosign signature attached to the image; nothing about the image content changes in transit. The internal-registry repository name (`sealed/python` in the example) is yours to choose; many teams use a `sealed/` prefix or a parallel namespace to keep sealed and upstream copies easy to tell apart.

## One-shot copy with `skopeo`

[`skopeo`](https://github.com/containers/skopeo) does the same with a slightly different invocation:

```bash
skopeo copy \
  --src-creds <seal-username>:<seal-password> \
  --dest-creds <internal-username>:<internal-password> \
  docker://<seal-registry>/python:3.12.9-slim-bullseye-20250408T180700Z-sp2 \
  docker://<internal-registry>/sealed/python:3.12.9-slim-bullseye-20250408T180700Z-sp2
```

`skopeo` supports `dir://` and `oci://` destinations as well, which is convenient when staging an image to disk for transport across an air-gap (see below).

## Batch mirroring

Scripting the copy of many sealed images at once is a small wrapper around the per-image command. A list of canonical references (one per line) plus the registry-rewrite is enough:

```bash
while IFS= read -r ref; do
  dest="${ref/<seal-registry>/<internal-registry>/sealed}"
  crane copy "$ref" "$dest"
done < sealed-images.txt
```

Maintain `sealed-images.txt` alongside the rest of your container delivery configuration; reviewing the file shows which sealed images your fleet depends on.

## Continuous mirroring

If your internal registry supports remote / mirror repositories (Harbor, Artifactory, Nexus, ECR Pull Through Cache), configure a remote that points at Seal's hosted registry. The internal registry pulls images on first request and caches them locally. Subsequent pulls from the fleet hit the internal registry only.

The setup details are registry-specific. The configuration items each registry expects:

* **Upstream URL**: `https://<seal-registry>`
* **Authentication**: the Seal-issued username and password from [Authenticating with Seal's image registry](/setup-containers/seal-base-images/authenticating.md)
* **Repository scope**: limit the mirror to the sealed-image repositories your fleet actually uses, rather than mirroring the entire namespace

## Air-gapped transport

When the internal registry sits on the other side of an air-gap, the mirror happens in three phases:

1. **On the connected side**, save the sealed image to disk:

   ```bash
   skopeo copy \
     --src-creds <seal-username>:<seal-password> \
     docker://<seal-registry>/python:3.12.9-slim-bullseye-20250408T180700Z-sp2 \
     oci-archive:python-sealed.tar
   ```
2. **Transport** the archive across the air-gap using whatever transport patterns your environment already uses for crossing the boundary. See [On-prem and air-gapped environments](/setup-apps-os/on-prem.md) for the broader workflow this fits into.
3. **On the air-gapped side**, push the archive into the internal registry:

   ```bash
   skopeo copy \
     --dest-creds <internal-username>:<internal-password> \
     oci-archive:python-sealed.tar \
     docker://<internal-registry>/sealed/python:3.12.9-slim-bullseye-20250408T180700Z-sp2
   ```

The image, signature, and attestations cross the boundary intact.

## Updating the `FROM` reference

Once mirrored, repoint every `Dockerfile` `FROM` line that referenced `<seal-registry>` to `<internal-registry>` instead:

```dockerfile
# Before
FROM <seal-registry>/python:3.12.9-slim-bullseye-20250408T180700Z-sp2

# After
FROM <internal-registry>/sealed/python:3.12.9-slim-bullseye-20250408T180700Z-sp2
```

The image is identical; only the registry hostname changes. Builds against the mirrored image produce the same output as builds against the original.

## Verifying after a mirror

After the copy, verify the signature against the mirrored image with the same cosign key you would use against the original. The signature attests to the image content, not the registry it lives in, so a correctly mirrored image still verifies:

```bash
cosign verify --key <seal-cosign-public-key> \
  <internal-registry>/sealed/python:3.12.9-slim-bullseye-20250408T180700Z-sp2
```

A failed verify after a mirror almost always means the copy step dropped a manifest entry; re-running `crane copy` or `skopeo copy` with the same arguments usually clears it.

## Related

* [Pulling a sealed base image](/setup-containers/seal-base-images/pulling.md): the direct-pull path that this page is the alternative to.
* [Authenticating with Seal's image registry](/setup-containers/seal-base-images/authenticating.md): the credentials your `crane` / `skopeo` invocations need on the Seal-registry side.
* [Cryptographic signing and hash verification](/trust/signing.md): the cosign-based verification used in the snippets above.
