podman is not docker

podman is not docker .. yeah. That's an understatement.

This example is a one-shot container. It's not a pod (that might be an upcoming post). I'm running one command in the container and then I remove it. The task is to do some async calls (usually less than 10,000) to Business Central and log the results. The log must be persistent.

On with the show.

# Dockerfile
FROM python:3.11-buster
WORKDIR /usr/src/myapp

ADD src ./src
COPY .env .
COPY poetry.lock .
COPY pyproject.toml .

RUN curl -sSL https://install.python-poetry.org | python3 -
ENV PATH="/root/.local/bin:$PATH"
RUN poetry config installer.max-workers 10
RUN poetry install --only main

One image, based on python3.11-buster and a src folder containing the application.

My application must have persistent logging. Coming from docker one would assume that a VOLUMES directive in the Dockerfile or, my preferred method:

# docker-compose.yaml
version: "3"
    container_name: amazingapp
    env_file: .env
    build: .
      - ./logs:/usr/src/myapp/logs

docker-compose would do the trick. Well. I believe that I've read, somewhere, that docker-compose should just work with podman, but even this minuscule example fails. The VOLUME directive does nothing.

So, enter podman-compose.

Does it work? Yes, yes it does. Very well actually.

$ podman-compose -f docker-compose.yaml build
$ podman-compose -f docker-compose.yaml run --rm amazingapp \

To my surprise, this did not create persistent volumes visible for podman volume ls. But it works just as it should.

You might find it easier to run this just using plain podman. After all, it's one less dependency and is perhaps more future-proof.

$ podman build --tag amazingapp:latest .
$ podman run -it --name myhoovercraftisfulofeels \
  ./logs:/usr/src/myapp/logs:rw,z \
  localhost/amazingapp:latest \

Again, it works and no volumes are created.

If you instead execute /bin/bash (replacing /path/to/my/script) you can inspect your running container and find that - indeed the logs directory are being mounted inside the container at the WORKDIR path.

You have no freaking idea how long it took me to realize that WORKDIR was the crux of this matter!
$ podman inspect -f '{{.Mounts}}' <containername>
[{bind  /home/USER/path/to/logs /usr/src/myapp/logs  \
  [rbind] true rprivate}

So what are then volumes?

In a pod where you typically would use a kubernetes compatible yaml description you would add a persistentVolumeClaim for each volume. This does basically the same as

$ podman volume create amazingapp-volume

Inspecting the volume

$ podman volume inspect amazingapp-volume
          "Name": "amazingapp-volume",
          "Driver": "local",
          "Mountpoint": "/home/USER/.local/share/containers/storage/ \
          "CreatedAt": "2023-09-26T08:26:04.236862704+02:00",
          "Labels": {},
          "Scope": "local",
          "Options": {},
          "MountCount": 0,
          "NeedsCopyUp": true,
          "LockNumber": 3

Using the volume instead of /home/USER/path/to/logs

$ podman run -it --name myhoovercraftisfulofeels \
  -v amazingapp-volume:/usr/src/myapp/logs:rw,z \
  localhost/amazingapp:latest \

Where's your log? It's at:
not particularly convenient.

There are other mounting options as well:

$ podman run -it --name myhoovercraftisfulofeels \
--mount type=bind,source=/home/USER/path/to/logs,\ 
target=/usr/src/amazingapp localhost/amazingapp:latest \

Using mount this way, you'll probably run into SELinux problems. Adding --privileged might help, but I'd just stick to the -v and rw,z option to avoid turning off SELinux for the entire container process.

So here we are. This has been a long arduous journey. podman and docker are just different enough to really really trip you up. The takeaways are:

  • podman is better

  • docker has got better documentation

  • and a heck of a lot larger installed base

  • but you'll find the answer to your podman question

  • eventually