I have been using Docker since late 2013 and for me and many others Docker has revolutionised the way we build, package, and deploy software. As a community we are grateful to Docker and its creators. Docker is one of the first tools that I install on my dev machine. It used to be always running on my MacBook and anytime I wanted to try a new technology I preferred to install it using Docker. Just do a docker run <tech>
and you are good to go. But, this has changed in the last couple of years. Docker for Mac is still installed but I no longer keep it running. The main reason for that has been the amount of resources it consumes, distracting fan noise, and MacBook becoming too hot. There are many issues filed in the Docker for Mac issue tracker on Github where developers have shared similar experience. Still, I kept using it as there was no good alternative available.
A couple of weeks back I learnt that Docker has changed its monetization strategy. Docker Desktop (Docker for Mac and Docker for Windows) will soon require subscription. From the Docker blog published on 31st August 2021 I quote:
- Docker Desktop remains free for small businesses (fewer than 250 employees AND less than $10 million in annual revenue), personal use, education, and non-commercial open source projects.
- It requires a paid subscription (Pro, Team or Business), starting at $5 per user per month, for professional use in larger businesses. You may directly purchase here, or share this post and our solution brief with your manager.
- While the effective date of these terms is August 31, 2021, there is a grace period until January 31, 2022 for those that require a paid subscription to use Docker Desktop.
For organizations which are not small as per Docker’s definition of small will be required to pay a monthly per user subscription. I am not saying whether it is a good or bad strategy on behalf of Docker to monetize Docker Desktop. I personally don’t want to pay for Docker Desktop as I don’t care much about a fancy GUI. Most of my work is done using Docker CLI. This prompted me to look for other alternatives that can replace Docker Desktop.
In my research I found Podman, an open source project by Red Hat a worthy candidate to replace Docker Desktop. I wanted to take Podman for a test run to see if it is ready to replace Docker for Mac on my machine. My short answer is that it still needs more work. It is not a 1-1 replacement. You will have to make small changes.
In this post, I will show how we can use Podman with a real world application. Conduit is a real world blogging platform similar to Medium.com. Conduit was created by developers part of the realworld community.
You can clone the repository on your local machine by running the following Git command on your terminal.
git clone git@github.com:shekhargulati/conduit-podman-demo.git
Please change directory to conduit-podman-demo
Installing Podman on Mac
We will use Homebrew package manager to install Podman on our machine. Please run the following command.
brew install podman
The above install Podman CLI. You still need to run a Podman managed virtual machine on your host.
To do that run following commands:
podman machine init
podman machine start
You can list Podman managed VMs by running the following command.
podman machine list
NAME VM TYPE CREATED LAST UP
podman-machine-default* qemu 5 days ago Currently running
You can view installation information using podman info
command.
Pull and Run Docker Images
Before we move on to conduit application let’s run the basic pull and run commands to get confidence that podman works for the most basic use case.
You can list images using images sub-command as shown below.
podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
Since this is a clean installation no image is found.
There is a one-to-one mapping between Docker and Podman commands. Just use podman
instead of docker
.
Let’s pull the Nginx image using the command shown below.
podman pull nginx
You will be greeted by the following error message. Not a great start. Something as simple as pulling an image failed.
Error: failed to parse "X-Registry-Auth" header for /v3.3.1/libpod/images/pull?alltags=false&arch=&authfile=&os=&password=&policy=always&quiet=false&reference=nginx&username=&variant=: error storing credentials in temporary auth file (server: "https://index.docker.io/v1/", user: ""): key https://index.docker.io/v1/ contains http[s]:// prefix
The reason for this is that Docker for Mac has created a file called ~/.docker/config.json
that has an empty entry for https://index.docker.io/v1/
registry as shown below. Podman expects credentials since they are not present it fails.
{
"auths": {
"https://index.docker.io/v1/": {}
},
"credsStore": "desktop",
"experimental": "disabled",
"stackOrchestrator": "swarm",
"currentContext": "default"
}
The fix is simple. You have to remove https://index.docker.io/v1/
entry as shown below.
{
"auths": {
},
"credsStore": "desktop",
"experimental": "disabled",
"stackOrchestrator": "swarm",
"currentContext": "default"
}
Let’s try to pull the image again. This time we get another error as shown below. Podman is not doing great. Getting started experience needs improvement especially people coming from Docker world.
Error: short-name resolution enforced but cannot prompt without a TTY
It turns out we have to specify the complete docker image URL as shown below.
podman pull docker.io/library/nginx:latest
The above will pull the image and you should be able to see that as shown below.
podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/library/nginx latest ad4c705f24d3 15 hours ago 138 MB
Let’s now try to run the Nginx container from the image we just pulled.
To run the container we will use our usual run
sub-command as shown below. As you can see again it is 1-1 mapped to the docker run
command.
podman run --name nginx -d -p 8000:80 nginx:latest
Then, you can list containers
podman ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1ff585e884b4 docker.io/library/nginx:latest nginx -g daemon o... 3 seconds ago Up 3 seconds ago 0.0.0.0:8000->80/tcp nginx
So, we see our running container. Yay!
Now, I was expecting I should be able to access curl http://localhost:8000
but I got following error
curl: (7) Failed to connect to localhost port 8000: Connection refused
In the current Podman version(3.3.1) port forwarding from host to VM doesn’t work by just specifying -p
option. You will have to use bridge
network. This issue is now fixed and will be available in 3.3.2.
Stop and remove the container
podman stop nginx && podman rm nginx
Again, run the container but this use --network bridge
as well.
podman run -d --name nginx --network bridge -p 8000:80 nginx:latest
Now, if we use curl we see default nginx page.
curl http://localhost:8000
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
Now, we know that we can get pull and run working. Let’s move ahead.
Setting docker
alias
Since podman has one-to-many mapping with Docker CLI commands it is suggested that you set docker
alias as shown below. This will also ensure that we don’t have to change our development workflow. The expectation is that it will make the switch seamless.
alias docker=podman
We will see later in the post that it is not sufficient. The better approach is to use symlink. I will cover that later. Let me not jump ahead.
Running conduit application in Podman containers
If you have not cloned the demo application yet please do that now so that you can try it on your machine as well.
git clone git@github.com:shekhargulati/conduit-podman-demo.git
Change directory to conduit-podman-demo
.
conduit
is like most modern web applications. It has a frontend component and a backend component. Frontend is a Vue application that we run in Nginx and Backend API server is a Spring Boot app that uses MySQL database.
-rw-r--r-- 1 shekhargulati staff 216B Sep 13 15:18 README.md
drwxr-xr-x 13 shekhargulati staff 416B Sep 13 18:49 conduit-api
drwxr-xr-x 24 shekhargulati staff 768B Sep 13 15:18 conduit-frontend
We will first build the frontend image and then we will build the backend. Change directory to conduit-frontend
.
In the root of conduit-frontend
you will find the Docker file as shown below. It is using Docker multi-build feature. We first build the frontend in a nodejs image and then package the frontend distribution in nginx image.
FROM node:10.20 as builder
WORKDIR /home/ui
COPY . .
RUN npm install
ARG VUE_APP_API_URL
ENV VUE_APP_API_URL $VUE_APP_API_URL
RUN npm run build
FROM nginx
COPY --from=builder /home/ui/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Let’s build the image using docker build
command as shown below.
docker build --build-arg VUE_APP_API_URL=http://localhost:8080/api -t com.conduitapp/frontend .
We will get following error.
[1/2] STEP 1/7: FROM node:10.20 AS builder
[2/2] STEP 1/4: FROM nginx
Error: error creating build container: short-name resolution enforced but cannot prompt without a TTY
We already know the answer we have to use the full image name i.e. instead of node:10.20
we have to use docker.io/library/node:10.20
and same for nginx as shown below.
FROM docker.io/library/node:10.20 as builder
WORKDIR /home/ui
COPY . .
RUN npm install
ARG VUE_APP_API_URL
ENV VUE_APP_API_URL $VUE_APP_API_URL
RUN npm run build
FROM docker.io/library/nginx
COPY --from=builder /home/ui/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Run the docker build command again. This time image will get built successfully.
docker build --build-arg VUE_APP_API_URL=http://localhost:8080/api -t com.conduitapp/frontend .
We can check that our image is built.
podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
om.conduitapp/frontend latest adf828f74654 20 hours ago 139 MB
docker.io/library/nginx latest ad4c705f24d3 3 days ago 138 MB
docker.io/library/node 10.20 c5f1efe092a0 16 months ago 941 MB
We can run our frontend as shown below. It will fail to connect with API but still app will load.
docker run -p 8000:80 --network bridge com.conduitapp/frontend

In the browser console you will see API connection errors as well.
Let’s build backend image now. For this we will not use docker build
command directly. This project is configured to use palantir/gradle-docker plugin. In the build.gradle
you will notice following.
docker {
name "com.conduitapp/api"
dockerfile file('src/main/resources/docker/Dockerfile')
files bootJar.archivePath
buildArgs(['JAR_FILE': "${bootJar.archiveName}"])
pull false
}
The Dockerfile is shown below. We have changed base image to full name.
FROM docker.io/library/openjdk:8-jre-slim
ENV SPRING_DATASOURCE_URL jdbc:mysql://mysql:3306/conduit
ENV SPRING_DATASOURCE_USERNAME root
ENV SPRING_DATASOURCE_PASSWORD password
ENV APP_DIR /app
ARG JAR_FILE
ADD ${JAR_FILE} $APP_DIR/app.jar
WORKDIR $APP_DIR
EXPOSE 8080
CMD ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar","app.jar", "spring.datasource.url=${SPRING_DATASOURCE_URL}", "spring.datasource.username=${SPRING_DATASOURCE_USERNAME}","spring.datasource.password=${SPRING_DATASOURCE_PASSWORD}"]
Having successfully built the frontend image I was expecting this to be straightforward. The palantir/gradle-docker
internally using docker
CLI. Since I already have a docker
alias set I was confident it will be a cakewalk. It turned out I spent the next 4 hours making it work.
The Gradle build kept failing. Make sure you are inside conduit-api
directory.
./gradlew build docker
You must be wondering why. The reason was that I had Docker for Mac installed on my machine. I had stopped Docker for Mac but docker
CLI was still in the path. When Gradle plugin looks for docker
CLI it finds the actual Docker CLI not the alias. As I learnt, aliases are not expanded when calling them via a program. So, rather than using my docker
alias which points to the podman
Gradle plugin was using actual docker
CLI. This means it was calling the wrong Docker REST API endpoint. Rather than calling API under /v3.3.1/libpod/build
it was calling API under 1.4.0/build
(Docker REST API).
I figured this out by looking at Podman logs. First ssh into the Podman machine using podman machine ssh
and then run the following command.
podman --log-level=debug system service -t3600
I completely uninstalled Docker for Mac from my machine. After that I started seeing errors where docker
CLI was not available since alias expansion was not happening. Next, I set up symlink and things started working.
ln -s /usr/local/bin/podman /usr/local/bin/docker
After making this change, I was able to successfully build the image using ./gradlew build docker
.
docker images |grep conduit
com.conduitapp/frontend latest adf828f74654 21 hours ago 139 MB
com.conduitapp/api latest 2c79c1c574f7 2 days ago 235 MB
If we run this image it will fail as we have not yet started MySQL container.
docker run -p 8080:8080 com.conduitapp/api
Caused by: java.net.UnknownHostException: mysql: Name or service not known
at java.net.Inet6AddressImpl.lookupAllHostAddr(Native Method) ~[na:1.8.0_302]
at java.net.InetAddress$2.lookupAllHostAddr(InetAddress.java:929) ~[na:1.8.0_302]
at java.net.InetAddress.getAddressesFromNameService(InetAddress.java:1324) ~[na:1.8.0_302]
at java.net.InetAddress.getAllByName0(InetAddress.java:1277) ~[na:1.8.0_302]
at java.net.InetAddress.getAllByName(InetAddress.java:1193) ~[na:1.8.0_302]
at java.ne.InetAddress.getAllByName(InetAddress.java:1127) ~[na:1.8.0_302]
at com.mysql.jdbc.StandardSocketFactory.connect(StandardSocketFactory.java:188) ~[mysql-connector-java-5.1.47.jar!/:5.1.47]
at com.mysql.jdbc.MysqlIO.<init>(MysqlIO.java:301) ~[mysql-connector-java-5.1.47.jar!/:5.1.47]
... 53 common frames omitted
Let’s run a MySQL container and connect it with the app.
It took some time to figure this out. Podman does not have a concept of --link
nor two containers connected if they are part of the same network. You have to create a pod object and then create containers inside that pod.
podman pod create -p 8080:8080 --network bridge --name conduit
Now, we will create MySQL and API containers.
docker run -d --name mysql --pod conduit -e MYSQL_ROOT_PASSWORD=password -e MYSQL_DATABASE=conduit docker.io/library/mysql:5.6
docker run --pod conduit com.conduitapp/api
The logs confirm that API server is successfully able to connect to MySQL.
2021-09-16 06:04:22.674 INFO 1 --- [ main] .m.m.a.ExceptionHandlerExceptionResolver : Detected @ExceptionHandler methods in customizeExceptionHandler
2021-09-16 06:04:22.832 INFO 1 --- [ main] o.s.b.a.w.s.WelcomePageHandlerMapping : Adding welcome page: class path resource [static/index.html]
2021-09-16 06:04:24.188 INFO 1 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2021-09-16 06:04:24.199 INFO 1 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Bean with name 'dataSource' has been autodetected for JMX exposure
2021-09-16 06:04:24.230 INFO 1 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Located MBean 'dataSource': registering with JMX server as MBean [com.zaxxer.hikari:name=dataSource,type=HikariDataSource]
2021-09-16 06:04:24.421 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2021-09-16 06:04:24.449 INFO 1 --- [ main] io.spring.RealworldApplication : Started RealworldApplication in 23.531 seconds (JVM running for 25.509)
If you hit curl command
curl --silent http://localhost:8080
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Conduit App</title>
</head>
<body>
<pre>
dddddddd
CCCCCCCCCCCCC d::::::d iiii tttt
CCC::::::::::::C d::::::d i::::i ttt:::t
CC:::::::::::::::C d::::::d iiii t:::::t
C:::::CCCCCCCC::::C d:::::d t:::::t
C:::::C CCCCCC ooooooooooo nnnn nnnnnnnn ddddddddd:::::d uuuuuu uuuuuu iiiiiiittttttt:::::ttttttt
C:::::C oo:::::::::::oo n:::nn::::::::nn dd::::::::::::::d u::::u u::::u i:::::it:::::::::::::::::t
C:::::C o:::::::::::::::on::::::::::::::nn d::::::::::::::::d u::::u u::::u i::::it:::::::::::::::::t
C:::::C o:::::ooooo:::::onn:::::::::::::::nd:::::::ddddd:::::d u::::u u::::u i::::itttttt:::::::tttttt
C:::::C o::::o o::::o n:::::nnnn:::::nd::::::d d:::::d u::::u u::::u i::::i t:::::t
C:::::C o::::o o::::o n::::n n::::nd:::::d d:::::d u::::u u::::u i::::i t:::::t
C:::::C o::::o o::::o n::::n n::::nd:::::d d:::::d u::::u u::::u i::::i t:::::t
C:::::C CCCCCCo::::o o::::o n::::n n::::nd:::::d d:::::d u:::::uuuu:::::u i::::i t:::::t tttttt
C:::::CCCCCCCC::::Co:::::ooooo:::::o n::::n n::::nd::::::ddddd::::::ddu:::::::::::::::uui::::::i t::::::tttt:::::t
CC:::::::::::::::Co:::::::::::::::o n::::n n::::n d:::::::::::::::::d u:::::::::::::::ui::::::i tt::::::::::::::t
CCC::::::::::::C oo:::::::::::oo n::::n n::::n d:::::::::ddd::::d uu::::::::uu:::ui::::::i tt:::::::::::tt
CCCCCCCCCCCCC ooooooooooo nnnnnn nnnnnn ddddddddd ddddd uuuuuuuu uuuuiiiiiiii ttttttttttt
</pre>
</body>
</html>
Now, we will run our frontend container.
docker run -p 8000:80 --network bridge com.conduitapp/frontend
You can see your web app running at http://localhost:8000

But, I use docker-compose
I couldn’t get the docker-compose setup working completely. The whole environment boots up but ports are not forward to the local machine. So, from my MacBook I am unable to access the app. If I go inside the VM I am able to do that.
You will have to install podman-compose
first.
pip3 install podman-compose
Now, from inside the conduit-api
directory run the podman-compose up
command. The whole setup boots up cleanly but as mentioned above I can’t access it from my local machine. If you are aware of the solution please share it with me. I will update this article.
Connecting to Podman Unix Socket REST API
To make it work we have to forward Unix socket on local machine.
ssh -nNT -L/tmp/podman.sock:/run/user/1000/podman/podman.sock ssh://core@localhost:54766
You can check connection URL by running following command.
podman system connection list
Name Identity URI
podman-machine-default* /Users/shekhargulati/.ssh/podman-machine-default ssh://core@localhost:54766/run/user/1000/podman/podman.sock
podman-machine-default-root /Users/shekhargulati/.ssh/podman-machine-default ssh://root@localhost:54766/run/podman/podman.sock
Set DOCKER_HOST
export DOCKER_HOST=unix:///tmp/podman.sock
Now, you can use the API.
Conclusion
There are still rough edges in Podman so it will take some time before those are ironed out. It is not a 1-1 replacement yet. It requires tweaking things. I hope above the guide will help you migrate from Docker in case you take that path.
Need changes at multiple places in order to switch from docker to podman. I am curious about to know will it affect on k8s side as well.
Podman does not support volume mounts on OSX natively. It is currently a bigger challenge when considering Podman as an alternative to docker desktop on OSX.
Here is an ugly workaround – https://github.com/containers/podman/issues/8016#issuecomment-920015800