While working on my vibe coding project to implement containers’ supply chain workflows system, I decided to also replicate the experience that many enterprises currently have and implement the workflows using traditional CI/CD tools. As part of that I will also walk through the common scenarios for containers’ supply chain security.
One of the common patterns for securing the supply chain for container images and cloud-native artifacts is to mirror the images used in the enterprise from the public registry (i.e. Docker Hub) into a registry that the enterprise have full control of. There are two main reasons for that:
- First is to avoid the dependency on the public registry for availability reasons.
- And the second is to validate the container images before allowing their use internally.
For the second step, many enterprises don’t go beyond doing a vulnerability and eventually malware scan of the image. The reason being is that most of the images available in public registries do not have any additional metadata to act on. Note that this is changing with the growing offerings of hardened images pioneered first by Chainguard.
In this post, I will only concentrate on the implementation of the mirroring functionality using GitHub Actions.
Implementing a GitHub Action to mirror container images from DockerHub to any other registry is trivial. You can use any tool like crane, oras, regctl, or skopeo. For this first version of the mirroring functionality the tool doesn’t really matter because the functionality is very simple:
- Check if the destination repository has the image
- If not, copy the image from the source into the destination
I have an example of the image mirror GitHub Action implementation for python:3.14-slim image. Here is also an example run of the image mirror action that copied the image. It is very easy to create actions for other images using this example. The action is split in two:
_mirror_image.yml, which is the generic functionality that can does the check for existence and the copy of the image. This reusable part accepts parameters like the repos (source and destination) and the tag for the image.mirror-python.yml, which is the specific action for mirroringpython:3.14-slim. To implement a new action that mirrors another image, I just need to copy this one and modify the repos and the tags as you can see in the other two example actionsmirror-node.ymlandmirror-openjdk.yml.
Note, once again, that this implementation is the most minimalist one – the action doesn’t have extensive logic to keep track of what is already mirrored. For example, if I delete the image from the destination repository it will be re-synchronized again. If there is a new image, the new one will be synchronized (which is the intended behavior) but the old one will not be removed. What that means is that your destination repository will continue to grow as new images are pushed to the source one – something that you need to think about and implement actions to clean up.
This action implements the basic step of the containers supply chain security – acquisition of images your team depends on. It removes the dependency on a registry that you do not control and allows you to perform some quarantine actions on the images before you enable them for use internally.
In the next post, I will describe the steps you can take on the images while in quarantine and how you can implement this using GitHub actions.

Leave a Reply