Using Podman to run MongoDB on MacOS mounting data directory from MacOS host

; Date: Fri Oct 06 2023

Tags: Docker

Podman, the Docker alternative for running Docker containers, runs great on MacOS. But, it is a little tricky to get going, such as mounting directories into a container.

I'm developing a server using Node.js/TypeScript which persists its data to MongoDB. The simplest path is to run MongoDB in Docker, and to run Docker on my MacOS laptop I prefer to run Docker inside of Multipass. For setup of Multipass on MacOS to run Docker, see Replacing Docker Desktop with Multipass, to avoid Docker Desktop fees

However, for this project I want to have better control over things like memory allotment. The Multipass approach wasn't cutting it for me, and while I have the official Docker Desktop installed it doesn't work very well. That meant turning to Podman which I'd been avoiding because it doesn't support docker compose.

Supposedly running Podman instead of Docker is simple. You substitute the word podman in place of docker in commands, and you're good to go. Unfortunately it wasn't that simple.

To take a step back slightly - (podman.io) Podman is an open source project for building/executing Docker containers, that is "Kubernetes Ready". It offers some interesting features, such as root-less execution. The project was started by Redhat.

Installing Podman on MacOS

The official instructions say to use Homebrew to install Podman. Myself, I'm a MacPorts man, and prefer using it for installing open source tools. Fortunately the MacPorts project also has a Podman package.

Installation:

#### Homebrew
$ brew install podman
#### MacPorts
$ sudo port install podman

Either method results in the same software.

To initialize Podman, run:

$ podman machine init
$ podman machine start
Starting machine "podman-machine-default"
Waiting for VM ...
Mounting volume... /Users:/Users
Mounting volume... /private:/private
Mounting volume... /var/folders:/var/folders

This machine is currently configured in rootless mode. If your containers
require root permissions (e.g. ports < 1024), or if you run into compatibility
issues with non-podman clients, you can switch using the following command: 

    podman machine set --rootful

API forwarding listening on: /var/run/docker.sock
Docker API clients default to this address. You do not need to set DOCKER_HOST.

Machine "podman-machine-default" started successfully

This initializes a QEMU-based virtual machine within which a small Linux system is executing where a Podman service is available to run Docker containers. The machine is named podman-machine-default and notice that it mounts a few directories into the machine.

The machine can be tested:

$ podman run -it docker.io/library/busybox
Trying to pull docker.io/library/busybox:latest...
Getting image source signatures
Copying blob sha256:3f4d90098f5b5a6f6a76e9d217da85aa39b2081e30fa1f7d287138d6e7bf0ad7
Copying config sha256:a416a98b71e224a31ee99cff8e16063554498227d2b696152a9c3e0aa65e5824
Writing manifest to image destination
Storing signatures
/ #

This drops you into a shell in a Busybox container.

So.. it should be easy to run MongoDB, right?

Successfully running a MongoDB container on MacOS using Podman

For any database, like MongoDB, it is 100% necessary to persist the data directory on the host machine. Otherwise your database disappears in a puff of smoke as soon as you recreate the container.

This made running MongoDB on Podman surprisingly difficult.

But, first, there's the issue of docker compose support. The Compose specification is a relatively recent development of the Docker community. As a result the old docker-compose command is deprecated, and the official new way to run Compose files is with docker compose. Notice the lack of a hyphen.

Unfortunately the Podman community has not caught up with this. They suggest using a docker-compose-compatible tool with Podman. But, I am in disagreement and instead converted my Compose file into a podman run command.

podman run \
    --restart always \
    --expose 27017 \
    --publish 27017:27017 \
    --env MONGO_INITDB_ROOT_USERNAME=root \
    --env MONGO_INITDB_ROOT_PASSWORD=example \
    --volume /Users/david/Project/db/data:/data:rw \
    --detach \
    mongo

This was the final result after going through issues we're about to discuss.

No such file or directory

My first attempt at mounting the data directory used a different --volume option:

--volume /Volumes/Project/db/data/db:/data/db:rw 

My MacOS laptop has two drives in it, with the second drive containing several partitions. It's not important that I have it set up this way, but the key here is that the host machine mount point was not within /Users.

With that --volume option, I got this error:

Error: statfs /Volumes/Project/db/data/db: no such file or directory

I very carefully compared the path to the --volume command and to host system directories. Everything matched up correctly. This took many 10s of minutes until a (stackoverflow.com) Stackoverflow posting gave an answer.

Read back to the podman machine start command and you see a few lines talking about mounting directories.

Mounting volume... /Users:/Users
Mounting volume... /private:/private
Mounting volume... /var/folders:/var/folders

For some reason, this limits the directory hierarchies from which directories can be mounted into a container running under Podman. But, I did not know that at the time.

Specifically, it seems that with Podman, the --volume option is looking for directories inside the virtual machine where the Podman containers actually execute. This is different from how Docker Desktop runs where there is a seamless mounting of host directories into the container.

The solution is, when running podman machine init, to mount host directories into the machine. Those host directories can then be mounted into containers.

$ podman machine stop
$ podman machine rm podman-machine-default
$ podman machine init \
    --volume /Users/david:/Users/david \
    --volume /Volumes/Project/:/Volumes/Project
$ podman machine start

The first two commands get one back to a clean slate, recreating the Podman machine from scratch. The --volume option is not available with the podman machine start command, only with podman machine init.

You can inspect the directories mounted into the Podman virtual machine like so:

$ podman machine ssh
Connecting to vm podman-machine-default. To close connection, use `~.` or `exit`
Fedora CoreOS 38.20231002.2.2
Tracker: https://github.com/coreos/fedora-coreos-tracker
Discuss: https://discussion.fedoraproject.org/tag/coreos

[root@localhost ~]# ls /
Users  Volumes  bin  boot  dev  etc
home  lib  ...

Explore the directory further and you'll see that it's the host machine mounted into the Podman virtual machine. The --volume mount on the podman run command now works. But, with MongoDB there is another problem.

MongoDB: chown: changing ownership of '/data/db': Operation not permitted

The podman run command for running MongoDB got closer to running, except that podman ps showed that the container was not running, and that it had stayed up for less than one second.

Running podman logs _container name_ showed a zillion copies of this error:

chown: changing ownership of '/data/db': Operation not permitted
chown: changing ownership of '/data/db': Operation not permitted
chown: changing ownership of '/data/db': Operation not permitted

Thinking this had to do with directory permissions, I ran chmod 777 on the host directory. No change.

Another (stackoverflow.com) Stackoverflow posting gave a solution. Instead of mounting the volume to /data/db, mount it to /data instead.

Hence, instead of this option:

--volume /Volumes/Project/db/data/db:/data/db:rw 

The solution is to use:

--volume /Volumes/Project/db/data:/data:rw 

Or, specifically:

--volume /Users/david/Project/db/data:/data:rw

This way, MongoDB is not executing chown on the mount-point.

One way to verify MongoDB is running is:

$ mongosh --host localhost:27017 \
        --username root \
        --password example

While MongoDB is now running, and we can load it up with data, the host machine directory does not have the database.

$ ls ~/Project/db/data
configdb db
$ ls ~/Project/db/data/db

Exploring Podman volume mounts

As with Docker, you can inspect the details of a running container image.

$ podman inspect _container name_

This gives a very long JSON blob. The Mounts field is where information about volume mounts is stored.

You can inspect the volumes with:

$ podman volume ls
$ podman volume inspect _volume ID_

I found that Podman mounted a private volume at /data/db which exists as a directory inside the virtual machine, but does not map to the host directory. Inside the MongoDB Dockerfile, there is a directive VOLUME /data/db causing this directory to be a VOLUME.

Mounting a host directory to /data did not change the fact that /data/db is a volume, and Podman allocated a volume inside its private virtual machine.

To make an experiment, change the --volume option to this:

--volume /Users/david/Evoke/db/data:/david-data

The point is for the mount-point inside the container to be different from the VOLUME specified in the Dockerfile.

After restarting the container, start a shell inside the container like so:

$ docker exec -it mongodb bash

You can then explore the directories

root@b2c2743b9af2:/# ls /david-data/
configdb  db
root@b2c2743b9af2:/# ls /data/db
WiredTiger	   WiredTigerHS.wt			 collection-4--1401331427186642920.wt  index-1--1401331427186642920.wt	index-8--1401331427186642920.wt  sizeStorer.wt
WiredTiger.lock    _mdb_catalog.wt			 collection-7--1401331427186642920.wt  index-3--1401331427186642920.wt	index-9--1401331427186642920.wt  storage.bson
WiredTiger.turtle  collection-0--1401331427186642920.wt  diagnostic.data		       index-5--1401331427186642920.wt	journal
WiredTiger.wt	   collection-2--1401331427186642920.wt  docker-initdb.log		       index-6--1401331427186642920.wt	mongod.lock

The /david-data directory exists and has the contents from the host machine. The /data/db directory also exists, with MongoDB database files that you'll find in the private volume directory inside the Podman virtual machine.

Inside the MongoDB container, run this:

root@b2c2743b9af2:/# touch /david-data/db/foo
root@b2c2743b9af2:/# exit

And then on your host machine run this:

$ ls ~/Project/db/data/db/
foo

Hence, the /data-david directory is mounted from the host machine. We can create a file inside the container, and it exists in the host machine.

Therefore, the solution is to tell MongoDB to use a different data directory.

Changing the MongoDB data directory

It's relatively simple to do this.

podman run \
    --name mongodb \
    --user mongodb \
    --userns=keep-id:uid=999,gid=999 \
    --restart always \
    --expose 27017 \
    --publish 27017:27017 \
    --env MONGO_INITDB_ROOT_USERNAME=root \
    --env MONGO_INITDB_ROOT_PASSWORD=example \
    --volume /Users/david/Project/db/data:/project-data:rw \
    --detach \
    mongo --dbpath /project-data/db

One can pass mongod command-line options simply by adding them after the mongo container name.

Unfortunately there were a large number of problems which ensued having to do with mapping user IDs between the host user ID and the container user ID.

The --user and --userns options were an attempt to handle that mapping. The --user option changes the user ID used within the container to be mongodb. That user name has the ID of 999.

Setting a memory limit with Podman

Another desired feature was to set a higher memory limit:

$ podman machine stop
$ podman machine set --memory 4096
$ podman machine start

This changes the memory limit without having to completely rebuild the podman machine.

Conclusion

It seemed like it would be an easy task to bring up MongoDB under Podman on MacOS.

Unfortunately there are a large number of problems. In retrospect, it seems Podman is probably not as polished as Docker Desktop. With Docker Desktop issues like mapping container UIDs to the MacOS host UIDs is handled transparently, as are mounting host directories into the container. Both of those tasks were much more difficult using Podman.

As a result, I'm now installing MongoDB using MacPorts.

About the Author(s)

(davidherron.com) David Herron : David Herron is a writer and software engineer focusing on the wise use of technology. He is especially interested in clean energy technologies like solar power, wind power, and electric cars. David worked for nearly 30 years in Silicon Valley on software ranging from electronic mail systems, to video streaming, to the Java programming language, and has published several books on Node.js programming and electric vehicles.