Now that I’ve enticed you into clicking on this article, I’ve got some bad news for you: I will be using the dreaded Docker Engine* and its implementation of an image registry for this short post.
Sorry about that.
* [UPDATE]: Of course, even though Docker is ubiquitous in the industry (largely because they got there first), that doesn’t mean that we need to play their cynical and diabolical game. Added to this post is a new section on using the Podman utility to accomplish the same goal.
I have been working on some projects for funzies, and I’m creating test images that I’ll need to download (pull) from a container registry. There are two scenarios I wanted to avoid:
- I didn’t want to use the same container registry as my employer. Accessing and storing credentials and mixing work-in-progress / half-baked ideas with other images doesn’t appeal to anyone.
- I didn’t want to push to Docker Hub but wanted more control over the environment.
This led me to wonder about how involved it would be to run a local (private) image registry for development. Of course, I’ve thought this in passing before, but always just opted for the Docker Hub public registry option like a good little lemming.
Well, it turns out that hosting a local, private image registry is extremely easy to set up (or, stand up, if I were a cool guy).
There is a open-source project called Distribution, and this is an implementation of the OCI distribution specification for standardizing the distribution of content.
Setup
Create a private Docker registry (using the latest stable version at the time of writing):
$ docker run --rm -d -p 5000:5000 --name registry registry:2.8.3
Tag images with the prefix
localhost:5000/
to associate them with the local private registry.
Rename the image so it is push to the correct registry:
$ docker image tag ansible-playbook:latest localhost:5000/ansible-playbook:latest
Push an image to the private registry:
$ docker image push localhost:5000/ansible-playbook:latest
List the images available in the private registry:
$ curl http://localhost:5000/v2/_catalog
{"repositories":["ansible-playbook"]}
Pull the image:
$ docker pull localhost:5000/ansible-playbook:latest
Follow the logs:
$ docker logs -f registry
registry
is the name of the container.
Remove an image from the local registry:
$ docker image remove localhost:5000/ansible-playbook:latest
Stop the registry container and remove all the data from any anonymous volumes:
$ docker container stop registry
$ docker container rm --volumes registry
Use Case
A common use case would be to integrate this into a CI/CD pipeline. For instance, every push to the repository would kick off a build and push the build artifact (the image) to a publicly-accessible endpoint that would the use ssh
port forwarding to send it to my local machine (note that usually it would be pushed to Docker Hub, ECR or some other popular registry).
For this demo, I’m using Bitbucket Pipelines and ngrok
to set up the ssh
port forwarding.
On a side note, I wouldn’t use ngrok
unless you don’t have access to a public server which you can use to do the port forwarding. There’s no point in using ngrok
and tools like it when you can just use the foundation technology on which it’s built upon.
Dockerfile
FROM python:3.12.5-slim-bookworm
ARG ANSIBLE_CORE_VERSION=2.16.3
ARG USER=noroot
RUN apt-get update && apt-get install -y \
git \
openssh-client
RUN groupadd --gid 1000 $USER \
&& useradd \
--create-home \
--home-dir /home/$USER \
--uid 1000 \
--gid 1000 \
$USER
RUN mkdir -p /root/.ssh && \
touch /root/.ssh/known_hosts && \
ssh-keyscan -H bitbucket.org >> /root/.ssh/known_hosts
RUN --mount=type=ssh git clone git@bitbucket.org:kilgore-trout/ansible.git /home/$USER/ansible
USER $USER
ENV PATH="$PATH:/home/noroot/.local/bin"
RUN python -m pip install ansible-core==$ANSIBLE_CORE_VERSION argcomplete boto3 pywinrm && \
ansible-galaxy collection install \
ansible.windows \
community.general \
community.windows
WORKDIR /home/noroot/ansible
ENTRYPOINT ["ansible-playbook"]
Start ngrok
on your local machine, get the dynamically-generated endpoint, and paste it into the Bitbucket pipeline below (after which you’ll push to the repo).
bitbucket-pipelines.yaml
in the root of the repository:
image: atlassian/default-image:3
pipelines:
default:
- step:
script:
- export DOCKER_BUILDKIT=1
- docker build --ssh default=$BITBUCKET_SSH_KEY_FILE -t 2d1c-47-14-77-147.ngrok-free.app/ansible-playbook .
- docker image push 2d1c-47-14-77-147.ngrok-free.app/ansible-playbook
services:
- docker
Again, note that the domain here was dynamically generated by
ngrok
.
You’ll also have to create an ssh
keypair and register it with your Bitbucket pipeline, the repository in which you’re cloning, and, of course, with your Bitbucket account. This is outside the scope of this idiotic demo.
After a push
event, you’ll see the Bitbucket pipeline begin the build, after which you should see ngrok
generate some logs after its endpoint is hit and the subsequent forward to the local machine. The image should then be ready for download:
$ docker pull localhost:5000/ansible-playbook:latest
Using Podman
The good news is that you don’t need to use crappy Docker. Instead, we can use the superior Podman utility to pull and create a registry in the exact same way as we did with Docker:
$ podman run --rm -d -p 5000:5000 --name registry registry:2.8.3
It’s the same command except for
s/docker/podman
.
When attempting to push an image to the local registry, I got the following error:
$ podman image push localhost:5000/alpine:latest
Getting image source signatures
Error: trying to reuse blob sha256:63ca1fbb43ae5034640e5e6cb3e083e05c290072c5366fcaa9d62435a4cced85 at destination: pinging container registry localhost:5000: Get "https://localhost:5000/v2/": http: server gave HTTP response to HTTPS client
This is easily remedied by adding a bit of configuration:
/etc/containers/registries.conf.d/myregistry.conf
[[registry]]
location = "localhost:5000"
insecure = true
And now:
$ podman image push localhost:5000/alpine:latest
Getting image source signatures
Copying blob 63ca1fbb43ae done
Copying config 91ef0af61f done
Writing manifest to image destination
Storing signatures
$ curl http://localhost:5000/v2/_catalog
{"repositories":["alpine"]}
Weeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
Also, do yourself a favor. Uninstall Docker and add the following alias to bash
or whatever shell you use:
alias docker="podman"