; Date: Fri Oct 06 2023
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
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 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.
#### 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
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
--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 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
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.
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.
Stackoverflow posting gave a solution. Instead of mounting the volume to
/data/db, mount it to
Hence, instead of this option:
The solution is to use:
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:
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
/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
/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.
--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
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.
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.