So far, we've been working locally, and our images essentially exist only on our own computers for our personal use, mainly to avoid the need to install tools on our host machines. However, the real challenge is sharing this new setup with our coworkers who are also working on the same project. This is where Docker Hub comes into play. In simple terms, Docker Hub is like GitHub, but for Docker images. The first step is to create an account.
By the way, before we dive into this new topic, here's a useful command. If you want to start fresh and remove all your images, networks, and containers, you can use the system prune command with the -a
flag. If you only want to remove images that are not being used, simply avoid using the -a
flag.
$ docker system prune -a
Ok, login into your docker account from the CLI by typing docker login -u
, where <username> is your docker hub account username, don’t forget the password
$ docker login -u <username>
Password:
WARNING! Your password will be stored unencrypted in /home/<username>/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credential-stores
Login Succeeded
After the login we are ready to rock, so let’s create a dummy docker image using Alpine, something simple like
# Fetch a new image from alpine
FROM alpine
# install bash just for fun
RUN apk add --no-cache bash
# display a mesage
ENTRYPOINT ["echo", "Hola mundo"]
Build our crappy image BUT!!!, this time you have to name your image appending your docker hub username, otherwise it won be possible to upload your image
$ docker build -t <username>/dummy .
Upload our recent image to docker hub using docker push
Now, just for testing purposes lets pull this image in our machine, but first remove the one you have locally to avoid any confusion or conflict
$ docker image rm <username>/dummy
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
Use docker pull
command
$ docker run <username>/dummy
Unable to find image '<username>/dummy:latest' locally
latest: Pulling from <username>/dummy
43c4264eed91: Pull complete
1df2f3cc77e8: Pull complete
Digest: sha256:783d8414ebec4ea19eb9038401376949f67fe87012bab65b221c4d1ee99201ba
Status: Downloaded newer image for <username>/dummy:latest
Hola mundo
That’s it —end of the story. Well… 99% of the time, this is how you’ll use Docker Hub. But please, don’t, seriously—don’t just do that. It's like those meaningless Git commit messages we all regret later. We need to put some care into every image we upload, and good documentation is essential. Let’s follow best practices and, in the process, learn some new Docker tips along the way.
Improve your image
For this example, let’s go back to our OpenOCD image, but with the following additions and new recommendations:
- Always use tags for your base images. In our case, we are using the Linux Alpine version 3.20.3.
- Always use fixed versions of the packages you install in your images.
- Learn how to use your Linux package manager!
The reason for specifying package versions is to maintain consistency across all build versions of your image. If you always install the latest versions, the image will depend on when it was built, which could lead to different versions being used across your team. This can introduce inconsistency and potential issues, so it's important to lock down package versions to ensure everyone is working with the same setup.
# Fetch a new image from alpine version 3.17
FROM alpine:3.17
# install openocd version 12.0 revision 0
RUN apk add --no-cache openocd=0.12.0-rc2-r0
# display openocd version
ENTRYPOINT [ "openocd", "-v" ]
In the Dockerfile above, we are using Alpine 3.17, which is the version that comes with OpenOCD 12.0 release 0. If you need to install an older version of OpenOCD or other tools, it will depend on the distribution you are using.
The previous image only displays the OpenOCD version, which is pretty basic, perhaps even a bit useless, but it's sufficient for what we want to achieve: building the image, tagging it with a version, and then pushing it to Docker Hub. The tag should clearly indicate the version of your image, so it's easy to track and manage.
$ docker build -t <username>/openocd:12.0-r0 .
...
$ docker push <username>/openocd:12.0-r0
...
Got o docker hub and write a nice and pretty repository overview, you use Markdown notation, and please make sure to put some love specifying how to use your image, people and yourself form the future will thank you.
*OpenOCD* display version image, if you only run the image it only display the version
# Supported tags and respective Dockerfile links
- [`12.0-r0`](https://hub.docker.com/layers/diegomodular/openocd/12.0-r0/images/sha256-61729ac9b7eeb6337cb2f0652966372d819142d0b218132ea2bba6474c93273a?context=repo)
# How to use this image
Just run like any other image and prepare yourself to watch displyed OpenOCD version
```c
docker run <username>/openocd:0.12.0-r0
```
This is written in you Docker Hub page form your repo, with he following result. If you want some inspiration, this is the overview description for the alpine image
An image that just outputs the version isn't particularly useful, so let’s add more functionality. OpenOCD is a debug server, and as such, it has several ports for accepting connections from different services, such as:
- 3333 for GDB
- 4444 for Telnet
- 5555 for TCL
In our image, we need to expose these ports so they can be accessed when the container is running.
# Fetch a new image from alpine version 3.17
FROM alpine:3.17
# install openocd version 12.0 revision 0
RUN apk add --no-cache openocd=0.12.0-rc2-r0
# Expose openocd ports to accept connection from GDB 3333,
# Telnet 4444 and Tcl 5555
EXPOSE 3333
EXPOSE 4444
EXPOSE 5555
# display openocd version
ENTRYPOINT [ "openocd", "-v" ]
If we build and run the image in de-attached mode using flag -d
and then we with docker ps
we can see, the ports we should publish with flag -p
$ docker build -t <username>/openocd:12.0-r0 .
$ docker run --rm -d <username>/openocd:12.0-r0
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
22218661136f <username>/openocd:12.0-r0 "/bin/sh" 12 seconds ago Up 12 seconds 3333/tcp, 4444/tcp, 5555/tcp relaxed_faraday
Time to make our image useful, instead of only display version we are going to try to connect to a default board using CMD to pass arguments to openocd
# display openocd version
ENTRYPOINT [ "openocd" ]
CMD [ "-f", "board/st_nucleo_g0.cfg", "-c", "bindto localhost" ]
But something much better is to make these parameters free for a user to configure, we can use ENV to define a default board and a default ip address
# Fetch a new image from alpine version 3.17.3
FROM alpine:3.17.3
# install openocd version 12.0 revision 0
RUN apk add openocd=0.12.0_rc2-r0
# expose openocd ports to accept connection from GDB 3333,
# Telnet 4444 and Tcl 5555
EXPOSE 3333
EXPOSE 4444
EXPOSE 5555
# Paramter to select board and IP address
ENV IP_ADDR=localhost
ENV BOARD=board/st_nucleo_g0.cfg
# open a openocd connection
CMD [ "sh", "-c", "openocd -f ${BOARD} -c \"bindto ${IP_ADDR}\"" ]
You might be wondering why I changed the last two lines of the Dockerfile. The reason is that you cannot use ENV
variables directly in an ENTRYPOINT
or CMD
string. We need to make some adjustments to handle this properly. For more details on the pros and cons of this, check out this https://www.baeldung.com/ops/docker-entrypoint-environment-variables
Additionally, be sure to read more about ARG
and ENV
variable definitions in Docker here: https://phoenixnap.com/kb/docker-environment-variables
So now after the proper build we can run the container, in the following way it will open a connection to a Nucleo-G0B1RE board in localhost
$ docker run -it --rm --device=/dev/bus/usb:/dev/bus/usb -p 3333:3333 test
But if you want to change the board and the IP address to connect you can override these parameters with flag -e
$ docker run -it --rm --device=/dev/bus/usb:/dev/bus/usb -p 3333:3333 -e BOARD=board/st_nucleo_f4.cfg -e IP_ADDR=172.17.0.3 test
It also work in docker compose through directive environment
services:
open_server: # container name been used by the image to connect to openocd
image: open
ports: # port mapping
- 3333:3333
devices: # device mapping to usb ports
- /dev/bus/usb:/dev/bus/usb
environment: # set the Enviroment variables
- BOARD=board/st_nucleo_f4.cfg
- IP_ADDR=172.17.0.3
Getting back to our image we can push the new version of our image as a minor version like openocd:12.0.r1, because why not, after pushing the new version take a look at your docker hub repo and of course update the overview properly
$ docker build -t <username>/openocd:12.0-r1 .
$ docker push <username>/openocd:12.0-r1
A new major version
If you notice, Docker Hub handles repositories sort of the way GitHub does, allowing us to manage versions. This is important because, in the Docker world, everything should be implemented with specific versions to ensure consistency.
To version our OpenOCD image, it's as simple as assigning a new tag (as we did previously). For example, let’s say we want to migrate to OpenOCD version 12, revision 4. This would include using a new version of our base image, Alpine.
# Fetch a new image from alpine version 3.20
FROM alpine:3.20.3
# Document the purpose of the image and any additional details
LABEL maintainer="John Doe <john@example.com>"
LABEL description="Docker image for running OpenOCD for GDB, Telnet and Tcl connections"
# install openocd version 12.0 revision 4
RUN apk add openocd=0.12.0-r4
# expose openocd ports to accept connection from GDB 3333,
# Telnet 4444 and Tcl 5555
EXPOSE 3333
EXPOSE 4444
EXPOSE 5555
# Parameters to select board and IP address
ENV IP_ADDR=localhost
ENV BOARD=board/st_nucleo_g0.cfg
# open a openocd connection
CMD [ "sh", "-c", "openocd -f ${BOARD} -c \"bindto ${IP_ADDR}\"" ]
If you notice we add some metadata to enrich our image description
# Document the purpose of the image and any additional details
LABEL maintainer="John Doe <john@example.com>"
LABEL description="Docker image for running OpenOCD for GDB, Telnet and Tcl connections"
Build and push
$ docker build -t <username>/openocd:12.0-r4 .
$ docker push <username>/openocd:12.0-r4
We are not going to see this new information on docker hub, but you can make a docker inspect image to read this and some other information from the image
$ dcoker image inspect <username>/openocd:12.0-r4
...
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"IP_ADDR=localhost",
"BOARD=board/st_nucleo_g0.cfg"
],
"Cmd": [
"sh",
"-c",
"openocd -f ${BOARD} -c \"bindto ${IP_ADDR}\""
],
"ArgsEscaped": true,
"Image": "",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": {
"description": "Docker image for running OpenOCD for GDB, Telnet and Tcl connections",
"maintainer": "John Doe <john@example.com>"
}
...
One last thing. We prepared an image to run an application with an specific purpose, like connect to a hardware and keep listening to a port. This is not an image to serve as base for other images, since we are setting an entry point
Last but not least, remember to update your Docker Hub repository overview to include information about the new version, without removing details about the previous version.
One of the key benefits of using Docker is that we have versioned environments that are already tested and working. This is especially useful if, for any reason, you need to revert to a past version—perhaps to update or maintain previous projects.
Docker Registry
But what is a docker registry?, well this is basically the service where you store your docker images, you see in a team of developer you are going to distribute the images so they can build their containers with all the tools to start working, but in theory they a re not going to modify this image, usually there is going to be one person in charge of doing this if necessary.
Not only docker hub is the only registry service you can use, there is
Just to name some of them, There is also some self hosted apps like And also your own computer,
I choose not going into details about this topic because is not exclusive on embedded, and there already some other resource where to get more information