In this blog, you will learn how to use Podman with the built-in equivalent for Docker Compose. You will learn how to use Podman ‘kube play’ and how to deploy your Podman Pod to a local Minikube cluster. Enjoy!

1. Introduction

First reactions to the short intro will be: “you need to use Podman Compose for that!”. However, this blog is not about Podman Compose, but about using the basic concept of Podman by using Pods and deploy them to a Kubernetes cluster. Podman Compose is a different concept and deserves its own blog.

So, what will you do and learn in this blog? You will create a Pod locally, generate a Kubernetes yaml file for it and use the yaml file to recreate the Pod locally, but also for deploying it to a local Minikube Kubernetes cluster. You will notice that Podman has built-in functionality which ressembles the functionality of Docker Compose. In other words, you can accomplish exactly the same thing. So, you might not need something like Podman Compose at all!

Sources used in this blog are available at GitHub and the container image is available at DockerHub. The container image is build in a previous blog, you might want to check it out when you want to know more about Podman compared to Docker. The image contains a basic Spring Boot application with one REST endpoint which returns a hello message.

2. Prerequisites

Prerequisites needed for this blog are:

  • Basic Linux knowledge;
  • Basic container knowledge;
  • Basic Podman knowledge;
  • Basic Kubernetes knowledge.

3. Create Pod Locally

First thing to do, is to create a Podman Pod locally. The Pod contains two containers based on the same image.

Create the Pod with the following command. The port range 8080 up to and including 8081 is exposed externally. One container will expose the endpoint at port 8080 and the other container at port 8081.

$ podman pod create -p 8080-8081:8080-8081 --name hello-pod

Create both containers. With the environment variable added to container 1, you can configure the Spring Boot application to run on a different port. Otherwise the default port 8080 is used.

$ podman create --pod hello-pod --name mypodmanplanet-1 --env 'SERVER_PORT=8081' docker.io/mydeveloperplanet/mypodmanplanet:0.0.1-SNAPSHOT env
$ podman create --pod hello-pod --name mypodmanplanet-2 docker.io/mydeveloperplanet/mypodmanplanet:0.0.1-SNAPSHOT

Start the Pod.

$ podman pod start hello-pod

Check the status of the Pod. It is in the running state.

$ podman pod ps
POD ID        NAME        STATUS      CREATED        INFRA ID      # OF CONTAINERS
bef893686468  hello-pod   Running     3 minutes ago  1f0c0ebf2248  3

Verify whether you can access both endpoints. Both endpoints return the same hello message.

$ curl http://localhost:8080/hello
Hello Podman!
$ curl http://localhost:8081/hello
Hello Podman!

4. Generate Kubernetes Yaml

Based on the local Pod you created, you can generate a Kubernetes yaml file which will contain the configuration of your Pod. The generate kube command is used for that, followed by the Pod name hello-pod, followed by the file you want to generate the configuration to.

$ podman generate kube hello-pod -f kubernetes/hello-pod-1-initial.yaml

Take a closer look at the generated Kubernetes yaml file. It contains the Pod definition and the two containers that need to run in the Pod.

# Save the output of this file and use kubectl create -f to import
# it into Kubernetes.
#
# Created with podman-3.4.4
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2023-05-13T08:21:40Z"
  labels:
    app: hello-pod
  name: hello-pod
spec:
  containers:
  - args:
    - env
    image: docker.io/mydeveloperplanet/mypodmanplanet:0.0.1-SNAPSHOT
    name: mypodmanplanet-1
    ports:
    - containerPort: 8080
      hostPort: 8080
    - containerPort: 8081
      hostPort: 8081
    resources: {}
    securityContext:
      capabilities:
        drop:
        - CAP_MKNOD
        - CAP_NET_RAW
        - CAP_AUDIT_WRITE
  - image: docker.io/mydeveloperplanet/mypodmanplanet:0.0.1-SNAPSHOT
    name: mypodmanplanet-2
    resources: {}
    securityContext:
      capabilities:
        drop:
        - CAP_MKNOD
        - CAP_NET_RAW
        - CAP_AUDIT_WRITE
  restartPolicy: Never
status: {}

5. Recreate the Pod

Now that the configuration is stored in a Kubernetes yaml file, you can verify whether the Pod can be recreated based on this file. If this is the case, you can commit this file to a Git repository and you and your colleagues can use it to set up a development environment for example.

First, stop and remove the running Pod.

$ podman pod stop hello-pod
$ podman pod rm hello-pod

Verify whether the containers and Pod are really removed.

$ podman pod ps
POD ID      NAME        STATUS      CREATED     INFRA ID    # OF CONTAINERS
$ podman ps --all
CONTAINER ID  IMAGE       COMMAND     CREATED     STATUS      PORTS       NAMES

Start the Pod based on the generated Kubernetes yaml file with the play kube command.

$ podman play kube kubernetes/hello-pod-1-initial.yaml

Verify the status of the Pod. You will notice that the status is degraded.

$ podman pod ps
POD ID        NAME        STATUS      CREATED         INFRA ID      # OF CONTAINERS
a7eac7991adc  hello-pod   Degraded    53 seconds ago  8471932f5741  3

Verify the status of the containers. The status of container hello-pod-mypodmanplanet-2 shows you that something went wrong with this container. It exited for some reason.

$ podman ps --all
CONTAINER ID  IMAGE                                                      COMMAND     CREATED             STATUS                         PORTS                             NAMES
8471932f5741  k8s.gcr.io/pause:3.5                                                   About a minute ago  Up About a minute ago          0.0.0.0:8080-8081->8080-8081/tcp  a7eac7991adc-infra
0f25b7105d2b  docker.io/mydeveloperplanet/mypodmanplanet:0.0.1-SNAPSHOT  env         About a minute ago  Up About a minute ago          0.0.0.0:8080-8081->8080-8081/tcp  hello-pod-mypodmanplanet-1
840f307cb67b  docker.io/mydeveloperplanet/mypodmanplanet:0.0.1-SNAPSHOT              About a minute ago  Exited (1) About a minute ago  0.0.0.0:8080-8081->8080-8081/tcp  hello-pod-mypodmanplanet-2

Stop and remove the Pod again.

6. Fix the Kubernetes Yaml File

What went wrong here? When you take a closer look at the generated Kubernetes yaml file, you will notice that the generation of the file was a bit messed up for container mypodmanplanet-1. The environment variable is not correctly setup and it contains port mappings for port 8080 and for port 8081. The container mypodmanplanet-2 does not contain any port mapping at all.

...
spec:
  containers:
  - args:
    - env
    image: docker.io/mydeveloperplanet/mypodmanplanet:0.0.1-SNAPSHOT
    name: mypodmanplanet-1
    ports:
    - containerPort: 8080
      hostPort: 8080
    - containerPort: 8081
      hostPort: 8081
...

Let’s fix this in file hello-pod-2-with-env.yaml. Add the environment variable to container mypodmanplanet-1 and remove the port mapping for port 8080. Add the port mapping for port 8080 to container mypodmanplanet-2.

...
spec:
  containers:
  - env:
    - name: SERVER_PORT
      value: 8081
    image: docker.io/mydeveloperplanet/mypodmanplanet:0.0.1-SNAPSHOT
    name: mypodmanplanet-1
    ports:
    - containerPort: 8081
      hostPort: 8081
    resources: {}
 ...
  - image: docker.io/mydeveloperplanet/mypodmanplanet:0.0.1-SNAPSHOT
    name: mypodmanplanet-2
    ports:
      - containerPort: 8080
        hostPort: 8080
    resources: {}
...

Start the Pod again based on this new configuration.

$ podman play kube kubernetes/hello-pod-2-with-env.yaml

Verify the status of the Pod. It is now in the running state.

$ podman pod ps
POD ID        NAME        STATUS      CREATED         INFRA ID      # OF CONTAINERS
ea387d67b646  hello-pod   Running     41 seconds ago  c62a0f7f1975  3

Verify the status of the containers. All are running now.

$ podman ps --all
CONTAINER ID  IMAGE                                                      COMMAND     CREATED             STATUS                 PORTS                             NAMES
c62a0f7f1975  k8s.gcr.io/pause:3.5                                                   About a minute ago  Up About a minute ago  0.0.0.0:8080-8081->8080-8081/tcp  ea387d67b646-infra
97c47b2420cf  docker.io/mydeveloperplanet/mypodmanplanet:0.0.1-SNAPSHOT              About a minute ago  Up About a minute ago  0.0.0.0:8080-8081->8080-8081/tcp  hello-pod-mypodmanplanet-1
16875b941867  docker.io/mydeveloperplanet/mypodmanplanet:0.0.1-SNAPSHOT              About a minute ago  Up About a minute ago  0.0.0.0:8080-8081->8080-8081/tcp  hello-pod-mypodmanplanet-2

The endpoints are accessible as well.

$ curl http://localhost:8080/hello
Hello Podman!
$ curl http://localhost:8081/hello
Hello Podman!

7. Minikube

Let’s see whether you can use the generated Kubernetes yaml file in order to run the Pod in a Minikube Kubernetes cluster. Minikube allows you to run a Kubernetes cluster locally, mainly used during application development.

7.1 Generate Kubernetes Yaml

You need to generate the Kubernetes yaml file just like you did before, but this time you need to add the -s option to the command. This will generate a Kubernetes service, which allows you to access the containers from outside the Kubernetes cluster.

Execute the following command:

$ podman generate kube hello-pod -s -f kubernetes/hello-pod-3-minikube.yaml

Replace the Pod part from hello-pod-2-with-env.yaml in this newly generated yaml file hello-pod-3-minikube.yaml because the issues with the environment variable and port mapping are again present in this newly generated file.

The generated yaml file contains the following extra service description:

apiVersion: v1
kind: Service
metadata:
  creationTimestamp: "2023-05-18T07:07:31Z"
  labels:
    app: hello-pod
  name: hello-pod
spec:
  ports:
  - name: "8081"
    nodePort: 32696
    port: 8081
    targetPort: 8081
  - name: "8080"
    nodePort: 31435
    port: 8080
    targetPort: 8080
  selector:
    app: hello-pod
  type: NodePort
---
...

In short, without going into too much details, this service will map port 8080 to external port 31435 and it will map port 8081 to external port 32696. External means external to the Pod.

Before continuing, stop and remove the locally running Pod.

7.2 Install and Start Minikube

If you have not installed Minikube yet, it is now time to do so. The installation instructions can be found here. The following instructions are executed on a Ubuntu 22.04 OS.

$ curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
$ sudo install minikube-linux-amd64 /usr/local/bin/minikube

Start Minikube.

$ minikube start
πŸ˜„  minikube v1.30.1 on Ubuntu 22.04
✨  Using the docker driver based on existing profile
πŸ‘  Starting control plane node minikube in cluster minikube
🚜  Pulling base image ...
πŸƒ  Updating the running docker "minikube" container ...
🐳  Preparing Kubernetes v1.26.3 on Docker 23.0.2 ...
    β–ͺ Using image gcr.io/k8s-minikube/storage-provisioner:v5
πŸ”Ž  Verifying Kubernetes components...
🌟  Enabled addons: storage-provisioner, default-storageclass
πŸ’‘  kubectl not found. If you need it, try: 'minikube kubectl -- get pods -A'
πŸ„  Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default

Verify whether the default Minikube Pods are running and whether kubectl is available. You will need kubectl to load the Kubernetes yaml file.

$ minikube kubectl -- get pods -A
NAMESPACE     NAME                               READY   STATUS    RESTARTS      AGE
kube-system   coredns-787d4945fb-qb56z           1/1     Running   1 (86s ago)   2m52s
kube-system   etcd-minikube                      1/1     Running   2 (85s ago)   3m7s
kube-system   kube-apiserver-minikube            1/1     Running   2 (75s ago)   3m7s
kube-system   kube-controller-manager-minikube   1/1     Running   2 (85s ago)   3m4s
kube-system   kube-proxy-cl5bh                   1/1     Running   2 (85s ago)   2m52s
kube-system   kube-scheduler-minikube            1/1     Running   1 (91s ago)   3m7s
kube-system   storage-provisioner                1/1     Running   0             63s

7.3 Create Pod In Minikube

Now that a Minikube cluster is running, you can create the Pod based on the Kubernetes yaml file.

$ minikube kubectl -- create -f kubernetes/hello-pod-3-minikube.yaml
service/hello-pod created
Error from server (BadRequest): error when creating "kubernetes/hello-pod-3-minikube.yaml": Pod in version "v1" cannot be handled as a Pod: json: cannot unmarshal number into Go struct field EnvVar.spec.containers.env.value of type string

Unfortunately, this returns an error. The reason is that the environment variable port value must be enclosed with double quotes.

Replace the following snippet:

spec:
  containers:
    - env:
        - name: SERVER_PORT
          value: 8081

With the following:

spec:
  containers:
    - env:
        - name: SERVER_PORT
          value: "8081"

The new Kubernetes yaml file is hello-pod-4-minikube.yaml.

Execute the command again but this time with the new Kubernetes yaml file.

$ minikube kubectl -- create -f kubernetes/hello-pod-4-minikube.yaml 
pod/hello-pod created
The Service "hello-pod" is invalid: spec.ports[0].nodePort: Invalid value: 32696: provided port is already allocated

Now an error is returned indicating that the external port 32696 is already allocated.

Verify whether any service is running.

$ minikube kubectl -- get svc
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)                         AGE
hello-pod    NodePort    10.99.254.70   <none>        8081:32696/TCP,8080:31435/TCP   4m59s
kubernetes   ClusterIP   10.96.0.1      <none>        443/TCP                         6m56s

It appears that the Kubernetes service is created although initially the creation of the Pod failed. Also the Pod is created.

$ minikube kubectl -- get pod
NAME        READY   STATUS    RESTARTS   AGE
hello-pod   2/2     Running   0          3m8s

Remove the Pod and the Service.

$ minikube kubectl delete pod hello-pod
pod "hello-pod" deleted
$ minikube kubectl delete svc hello-pod
service "hello-pod" deleted

7.4 Final Attempt

Create the Pod and the Service again based on the hello-pod-4-minikube.yaml file. This time it is successful.

$ minikube kubectl -- create -f kubernetes/hello-pod-4-minikube.yaml
service/hello-pod created
pod/hello-pod created

Verify the status of the Service. The Service is created.

$ minikube kubectl -- get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                         AGE
hello-pod    NodePort    10.105.243.28   <none>        8081:32696/TCP,8080:31435/TCP   71s
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP                         12m

Check the status of the Pod, it is running.

$ minikube kubectl -- get pods
NAME        READY   STATUS    RESTARTS   AGE
hello-pod   2/2     Running   0          118s

But, can you access the endpoints?

Retrieve the IP address of the Minikube cluster.

$ minikube kubectl -- describe  pods | grep Node:
Node:             minikube/192.168.49.2

Verify whether the endpoints can be accessed using the Minikube IP address and the external ports defined in the Service. Beware that the external ports can be different when you generated the yaml files yourself.

$ curl http://192.168.49.2:32696/hello
Hello Podman!
$ curl http://192.168.49.2:31435/hello
Hello Podman!

7.5 Cleanup

In order to cleanup, you first stop the local Kubernetes cluster.

$ minikube stop
βœ‹  Stopping node "minikube"  ...
πŸ›‘  Powering off "minikube" via SSH ...
πŸ›‘  1 node stopped.

Finally, you delete the cluster.

$ minikube delete
πŸ”₯  Deleting "minikube" in docker ...
πŸ”₯  Deleting container "minikube" ...
πŸ”₯  Removing /home/<user>/.minikube/machines/minikube ...
πŸ’€  Removed all traces of the "minikube" cluster.

8. Conclusion

In this blog, you created a local Pod, generated a Kubernetes yaml file for it with Podman and used this yaml file to recreate the Pod locally and to create the Pod into a Minikube Kubernetes cluster. It did not work out-of-the-box, but with some minor tweaks, it worked just fine. The Kubernetes yaml file can be stored in a Git repository and shared with your colleagues just like you would do with a Docker Compose file. This way, a development environment can be set up and shared quite easily.