Featured image of post Getting lost in weeds of Podman

Getting lost in weeds of Podman

Switching to podman in practice. We explore how we can switch to podman in reality and how to make use of rootless containers

Podman is a tool developed by Redhat that is intended to replace docker at the command line level. It has full compatibility with docker CLI. At least this is true on Linux. Like docker it is written in golang and is quite performant. But unlike docker it does not have a lot of legacy warts, compatibility issues and quirks. It also has better integration with other tools in anything-but-docker space including buildah and skopeo

What are rootless containers

How Rootless Containers Work

A big selling point of using podman is that you will be able to run rootless containers. This means that the full virtualized container runs as the non-root user that you are logged in as usually. This is important in various aspects. One is that on shared servers where you just don’t get to have root, you can still get containers running. Also even on a machine where it is possible to become root for running docker containers, it is good in security point of view to be running as a non-privileged user.

At a more technical level the issue is bugs that enable malicious code to breakout of the virtualization env on to the host. And if it is able to exploit such a bug the attacker suddenly has root access to the host system. Now there are ways to mitigate those risks such as

  1. Specifying the USER for each of the Docker CMDs
  2. Specifying the USER for the docker RUN command on the command line
  3. Using a supervisor framework such as s6 or supervisord

But again, these are mitigating measures and not a solution to the underlying issue of running containers as the root user. rootless containers address the root cause by allowing you to run containers as a non privileged user. This means that even if there were to be a breakout, the attacker still would need a privilege escalation bug on the host env to be effective at attacking the host system

My use case for rootless containers

A friend had asked me for advice on best practices in FTPing files around. Now the best advice that I can give is not to FTP files around in 2022 anymore and there are much better data transfer mechanisms out there. But if you had to do it, there are ways to take most of the pain out of it such as using checksums and delta encoding and so on. While I was thinking of writing a blog post on those practices, I needed to write some test code. To test out this code, I needed a FTP server. Now I would rather not put any server on my primary workstation that need not be there and this meant I had to do this inside a docker container.

But I don’t have docker

I had tried to switch to podman sometime back but it was mostly limited to pulling images directly off docker/quay and using them. I had not gotten around to building images with podman or buildah much. But this time I had lucked out and the most commonly used FTP image seemed to lack support for running as a rootless container binding to ports higher than 1024 for the listening port. If you did not know about this - unprivileged users on linux cannot bind to ports lower than 1024 usually as explained here. There are ways around this for example by using authbind to achieve this via a setuid helper executable or using POSIX capabilities system

But each of those workarounds come with their own pitfalls, vulnarabilities and risks. Therefore the better way would be to use a non-root user and use a non-standard port. But docker does not usually run in non-root mode. So I switched to podman and I cloned the repo that I had chosen - docker-vsftpd to here and started from there. It was a simple change to add the new port. And add the change to the README. The full pull request is here

Adding finishing touches

I am a bit of a perfectionist and once I had started work on this repo I had to add a couple of finishing touches. The first was an automated build process. I had used github workflows and actions to achieve this. Here is the code for that

name: Build and Push Image
on: [ push,workflow_dispatch ]

    name: Build and push image
    environment: MainCI
    runs-on: ubuntu-20.04

    - uses: actions/checkout@v3

    - name: Shellcheck
      id: Shellcheck
      uses: ludeeus/action-shellcheck@master

    - name: Build Image
      id: build-image
      uses: redhat-actions/buildah-build@v2
        image: vsftpd
        tags: latest ${{ github.sha }}
        containerfiles: |

    # Podman Login action (https://github.com/redhat-actions/podman-login) also be used to log in,
    # in which case 'username' and 'password' can be omitted.
    - name: Push To docker.io
      id: push-to-docker
      uses: redhat-actions/push-to-registry@v2
        image: ${{ steps.build-image.outputs.image }}
        tags: ${{ steps.build-image.outputs.tags }}
        registry: docker.io/osadal
        username: osadal
        password: ${{ secrets.REGISTRY_PASSWORD }}

    - name: Print image url
      run: echo "Image pushed to ${{ steps.push-to-docker.outputs.registry-paths }}"

Things to note here are the build image action. Note that we are using buildah action to build the image and not docker. This means we keep the pipeline clear of docker even though we will still use docker.io to distribute the image.

Then note the secret mechanism. Github workflows manage secrets injection through envrionment variables as most other CI/CD pipelines do. The actual secret is input at the github settings level. Then it gets injected in to the CI/CD envrionment and is available for use by any action given access to it through the YAML code.

The next to do thing in my list is adding shellcheck support to the CI/CD pipeline. That will be left for another day. But for now I have cleared the shellcheck comments manually. The pull request is here. This mostly involves

  1. Breaking down assignment and export in to two lines. This is a good idea because otherwise they do not individually fail. So you can get a case where the generation of the variable fails but you still won’t know because the export worked and the return code was zero

  2. Convert backtick escaped sheel command output capturing to “$()” syntax. This is much better and less error prone. This deserves it’s own blog post so I will only point to POSIX spec for now.

  3. Quotin variable usage. You never know when spaces in values will trip you up otherwise.

The preamble becomes the main act

So there you have it. We set out to write about good FTP system practices instead wrote a blog post about how to use podman and shellcheck. The next one I promise will be on topic!

Built with Hugo
Theme Stack designed by Jimmy