Lightweight container tools: Skopeo

Lightweight container tools: Skopeo

14 December 2022

Jeroen Vermeylen

This is the third part in our series of blog posts about lightweight container tools. We’ve previously covered Podman and Buildah, and this time we will cover Skopeo. Skopeo is a tool to inspect and manage container images. Podman, docker, and various CLI tools already offer this functionality for local images, but Skopeo can also inspect and manage images in **remote repositories** and even **copy** between them.

Inspecting images

Using Skopeo, we can inspect images in a remote repository **without having to download them**. This mean we can view the information, config and tags of a container image by only downloading the metadata from the repository.

Before we can fetch this information, it is often required to log in to the remote repository. We can do so using the `skopeo login` command.

~ skopeo login docker.io
Username: *****
Password: 
Login Succeeded!

Next, we can request all the tags available for any given image with the `skopeo list-tags` command.

~ skopeo list-tags docker://alpine
{
    "Repository": "docker.io/library/alpine",
    "Tags": [
        "2.6",
        "2.7",
...
        "3.9.6",
        "edge",
        "latest"
    ]
}

As you can see, Alpine has a lot of versions stored in their Docker Hub repository(https://hub.docker.com/_/alpine/). Although we could achieve the same results by browsing to the repository, we prefer the CLI since we don’t have to leave our terminal window.

If we want to see more information on an image with a specific tag, we can utilize the `skopeo inspect` command. This output can get large quite fast because it also displays the available tags. Since we already listed the tags, we’ll use the `–no-tags` flag to hide the tags from the output.

 

~ skopeo inspect --no-tags docker://alpine:3.16.2
{
    "Name": "docker.io/library/alpine",
    "Digest": "sha256:bc41182d7ef5ffc53a40b044e725193bc10142a1243f395ee852a8d9730fc2ad",
    "RepoTags": [],
    "Created": "2022-08-09T17:19:53.47374331Z",
    "DockerVersion": "20.10.12",
    "Labels": null,
    "Architecture": "amd64",
    "Os": "linux",
    "Layers": [
        "sha256:213ec9aee27d8be045c6a92b7eac22c9a64b44558193775a1a7f626352392b49"
    ],
    "LayersData": [
        {
            "MIMEType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
            "Digest": "sha256:213ec9aee27d8be045c6a92b7eac22c9a64b44558193775a1a7f626352392b49",
            "Size": 2806054,
            "Annotations": null
        }
    ],
    "Env": [
        "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
    ]
}

We can also request the configuration of the container image. This contains more information about the layers, how it’s built, exposed ports, volumes, and so forth. To request this information, use the `skopeo inspect` command again, but this time with the `–config` flag. As the Alpine image has only a small config, we used the Python image for this example:

 

skopeo inspect docker://python:latest --config
{
    "created": "2022-10-26T03:05:10.447538Z",
    "architecture": "amd64",
    "os": "linux",
    "config": {
        "Env": [
            "PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
            "LANG=C.UTF-8",
            "GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D",
            "PYTHON_VERSION=3.11.0",
            "PYTHON_PIP_VERSION=22.3",
            "PYTHON_SETUPTOOLS_VERSION=65.5.0",
            "PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/6d265be7a6b5bc4e9c5c07646aee0bf0394be03d/public/get-pip.py",
            "PYTHON_GET_PIP_SHA256=36c6f6214694ef64cc70f4127ac0ccec668408a93825359d998fb31d24968d67"
        ],
        "Cmd": [
            "python3"
        ]
    },
    "rootfs": {
        "type": "layers",
        "diff_ids": [
            "sha256:d9d07d703dd5ba0b8e23bf7e1bd9f7e4093418a58dc9e470ca013d1c3a1b5bb5",
            "sha256:4ed121b04368587cfd083eac5f6210d00149f4262a298635e37002b1e8f2b885",
            "sha256:d38adf39e1dd1874b5ae52f844a8101c1a3b2b8f137ceab469348decbbe618e0",
            "sha256:d1dec9917839d08e32cc28fe0d9ae462449d4c5544f625828fb09aead956d5ec",
            "sha256:882fd36bfd35d8c0c12d8472686059e1a6943c23a1e12ff9c18bceec3027e47c",
            "sha256:6b183c62e3d75c58f15d76cc6b6bedadab02270bff6d05ed239c763a63dce306",
            "sha256:5b3f1ed98915a965b875c66105063a2a48629c557261e68c8ee25b76aed352cf",
            "sha256:53b8bfee7a0a316170beb65c30ef9a18b3af58bd63edadbe8abf2dbee9710749",
            "sha256:6f6e69c2c59238dbfce7a1364a7b4af0907e78fa8249a7f04b7f8582dc1a6a23"
        ]
    },
    "history": [
        {
            "created": "2022-10-25T01:43:42.439298174Z",
            "created_by": "/bin/sh -c #(nop) ADD file:26702ba2c3e94cb21cdb3c550cf01cf848823d160f3417b559116d4c718e5df0 in / "
        },
        {
            "created": "2022-10-25T01:43:42.951329892Z",
            "created_by": "/bin/sh -c #(nop)  CMD [\"bash\"]",
            "empty_layer": true
        },
...
        {
            "created": "2022-10-26T03:05:10.277010192Z",
            "created_by": "/bin/sh -c set -eux; \t\twget -O get-pip.py \"$PYTHON_GET_PIP_URL\"; \techo \"$PYTHON_GET_PIP_SHA256 *get-pip.py\" | sha256sum -c -; \t\texport PYTHONDONTWRITEBYTECODE=1; \t\tpython get-pip.py \t\t--disable-pip-version-check \t\t--no-cache-dir \t\t--no-compile \t\t\"pip==$PYTHON_PIP_VERSION\" \t\t\"setuptools==$PYTHON_SETUPTOOLS_VERSION\" \t; \trm -f get-pip.py; \t\tpip --version"
        },
        {
            "created": "2022-10-26T03:05:10.447538Z",
            "created_by": "/bin/sh -c #(nop)  CMD [\"python3\"]",
            "empty_layer": true
        }
    ]
}

Inspecting images not in repositories

The previous commands have always used remote container images by specifying the `docker://` prefix in front of the image name. However, the `skopeo inspect` command also supports other sources through the following transports: containers-storage, dir, docker, docker-archive, docker-daemon, oci, oci-archive, ostree, sif, and tarball.

If you’d like to inspect a **local image**, you could use the following:

 

~ skopeo inspect docker-daemon:alpine:3.16.2            
{
    "Name": "docker.io/library/alpine",
    "Digest": "sha256:6c43f7b4d8c89005a55bdd3bfd15daa1c36f81d880bdda5593921bc6d428a24a",
    "RepoTags": [],
    "Created": "2022-08-09T17:39:42.400443113Z",
    "DockerVersion": "20.10.12",
    "Labels": null,
    "Architecture": "arm64",
    "Os": "linux",
    "Layers": [
        "sha256:5d3e392a13a0fdfbf8806cb4a5e4b0a92b5021103a146249d8a2c999f06a9772"
    ],
    "LayersData": [
        {
            "MIMEType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
            "Digest": "sha256:5d3e392a13a0fdfbf8806cb4a5e4b0a92b5021103a146249d8a2c999f06a9772",
            "Size": 5572608,
            "Annotations": null
        }
    ],
    "Env": [
        "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
    ]
}
```

If you saved an image as a **tar file**, you can inspect the tar like this:
```
~ skopeo inspect tarball:alpine-3_16_2.tar            
{
    "Digest": "sha256:460b0247ac095501f5643fe0c1307ce8bb305167eed76d9e5dabc894454d9e2f",
    "RepoTags": [],
    "Created": "2022-11-02T11:54:02.052991701+01:00",
    "DockerVersion": "",
    "Labels": null,
    "Architecture": "arm64",
    "Os": "darwin",
    "Layers": [
        "sha256:66b0f10e840c41b23d5a7dfceab0ac000e1f627530786010416165d3930092e9"
    ],
    "LayersData": [
        {
            "MIMEType": "application/vnd.oci.image.layer.v1.tar",
            "Digest": "sha256:66b0f10e840c41b23d5a7dfceab0ac000e1f627530786010416165d3930092e9",
            "Size": 5581824,
            "Annotations": null
        }
    ],
    "Env": null
}

 Manage images

Now we know how to use Skopeo to get information about packages. The next step is to use Skopeo to transfer images between repositories. To get started with this, we’ll also have to log in to the second repository:

~ skopeo login containers.flowfactor.be
Username: *****
Password: 
Login Succeeded!

When we check which tags are available for the Alpine image, we can see that only 3.16.1 has been uploaded to the registry:

~ skopeo list-tags docker://containers.flowfactor.be/alpine                         
{
    "Repository": "containers.flowfactor.be/alpine",
    "Tags": [
        "3.16.1"
    ]
}

Since version 3.16.2 is available by now, we would like to push this to the repository. Skopeo can do so by using the `skopeo copy` command:

~ skopeo copy docker://alpine:3.16.2 docker://containers.flowfactor.be/alpine:3.16.2
Getting image source signatures
Copying blob 213ec9aee27d done  
Copying config 9c6f072447 done  
Writing manifest to image destination
Storing signatures
~ skopeo list-tags docker://containers.flowfactor.be/alpine
{
    "Repository": "containers.flowfactor.be/alpine",
    "Tags": [
        "3.16.1",
        "3.16.2"
    ]
}

The `skopeo delete` command is available if we want to delete the older image. This could be convenient if the image contains a bug or security issue and we want to make sure the developers can no longer use it.

~ skopeo delete docker://containers.flowfactor.be/alpine:3.16.1

~ skopeo list-tags docker://containers.flowfactor.be/alpine    
{
    "Repository": "containers.flowfactor.be/alpine",
    "Tags": [
        "3.16.2"
    ]
}

Syncing between repositories

If we ever want to sync all tags of an image to another repository, we can use the `skopeo sync` command. This is convenient if you want to create a mirror for a specific image. You could even use `–preserve-digests` so the digests of the images don’t get changed. This is required when something tries to pull an image with a specific digest through your mirror.

~ skopeo sync --src docker --dest docker alpine containers.flowfactor.be
Getting image source signatures
Copying blob sha256:2a3ebcb7fbcc29bf40c4f62863008bb573acdea963454834d9483b3e5300c45d
Writing manifest to image destination
Storing signatures
Getting image source signatures
Copying blob sha256:4dea34575ff3c97f7f897fcb8dbbceb88b791840971ada8b373f427c92843b97
Writing manifest to image destination
Storing signatures
Getting image source signatures
Copying blob sha256:9107ff4def222271fd4da41e2ec13d0cb34049c29102476ac8547579a60cd9b1
...
Getting image source signatures
Copying blob sha256:88ecf269dec31566a8e6b05147732fe34d32bc608de0d636dffaba659230a515
Copying config sha256:49b6d04814d5d56f2c3d3cfdaddb0ff16437d6089dbb624433aa4515da9f667e
Writing manifest to image destination
Storing signatures
Getting image source signatures
Copying blob sha256:213ec9aee27d8be045c6a92b7eac22c9a64b44558193775a1a7f626352392b49
Copying config sha256:9c6f0724472873bb50a2ae67a9e7adcb57673a183cea8b06eb778dca859181b5
Writing manifest to image destination
Storing signatures

After we’ve run the sync command, we can see that the Alpine image is now available with all of its tags:

~ skopeo list-tags docker://containers.flowfactor.be/alpine                         
{
    "Repository": "containers.flowfactor.be/alpine",
    "Tags": [
        "2.6",
        "2.7",
...
        "3.9.6",
        "edge",
        "latest"
    ]
}

Remarks

We wrote this article using a MacBook with a M1 chip. This is a CPU architecture and OS for which just a few images have been created. Because of this, Skopeo threw errors when we wanted to find images. We can use CLI flags to tell Skopeo to search for a specific architecture and OS:

--override-arch amd64 --override-os linux

Conclusion

In disconnected environments, Skopeo can be a lifesaver. It allows you to quickly move container images between repositories. The sync module can be especially handy when you want to create a mirror for certain images. Skopeo also lets you host your images on a registry where you can implement scanning for vulnerabilities.

Do you have any remaining questions about using Skopeo? Don’t hesitate to contact us, we’d love to help you out!

Related posts
No Comments

Sorry, the comment form is closed at this time.