There are quite a few Docker containers for running an NFS server in a docker container on Linux but none of them were satisfying my requirements. The first decision is whether to use the kernel-based NFS functions and permitting a privileged container to access these or put a user-space server (nfs-ganesha) into an unprivileged container.
I decided to do the latter but wanted every required process to run in one container instad of having half a dozen containers with one of the required functions having full access to shared file systems and some of them sharing sockets across containers – that’s not separation but confusion.
This was reducing the number of dockerized nfs-ganesha containers on github and dockerhub even further. In the end the decision was easy: Run a supervisord in the container and have it start all the components of nfs-ganesha. The real trick is the docker compose stanza (or the command line to run it) as at the time of image creation the required mount points are not known so EXPOSE commands will be futile and generating the necessary mount points fo the external volumes using a script evaluating environment variables is annoying, too. There is no reason not to restart the server to add another external volume; the server needs an update of its configuration files anyway. So mounting yet another volume on the root path of the export subdirectory (usually /export or /srv/nfs) is keeping things simpler.
So the final docker-compose file is looking like this:
nfs-server:
build:
context: ./nfs-server
dockerfile: Dockerfile
image: registry.bnc.net/ap/nfs-server
container_name: nfs-server
hostname: nfs-server
restart: no # unless-stopped
sysctls:
- net.ipv6.conf.all.disable_ipv6=0
ports:
- "111:111/udp"
- "2049:2049/tcp"
networks:
- front
volumes:
source: ${PROJECT_ROOT}/nfs-server/volumes/ganesha
target: /etc/ganesha
read_only: true
- type: bind
source: ${PROJECT_ROOT}/nfs-server/volumes/export
target: /export
read_only: false
- type: bind
source: ${PROJECT_ROOT}/nfs-server/volumes/testvol
target: /export/testvol
read_only: false
The payload section of the Dockerfile should be looking like this
# Payload
RUN apt update && \
apt install -q -y --no-install-recommends \
dbus \
netbase \
nfs-common \
nfs-ganesha \
nfs-ganesha-mem && \
apt clean && \
rm -rf /var/lib/apt/lists/* && \
mkdir -p /run/rpcbind /export /var/run/dbus /var/run/ganesha && \
touch /run/rpcbind/rpcbind.xdr /run/rpcbind/portmap.xdr && \
chmod 755 /run/rpcbind/* && \
chown messagebus:messagebus /var/run/dbus
COPY files/supervisor/cron.conf \
files/supervisor/ganesha.conf \
files/supervisor/idmapd.conf \
files/supervisor/rpcbind.conf \
files/supervisor/statd.conf \
/etc/supervisor/conf.d/
and the supervisord.conf
[supervisord]
nodaemon=true
logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log)
pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
childlogdir=/var/log/supervisor ; ('AUTO' child log dir, default $TEMP)
[unix_http_server]
file = /var/run/supervisor.sock
chmod = 0700
chown= root:root
username = super
password = totallyuselessthingshere
[inet_http_server]
port = 127.0.0.1:9001
username = super
password = evenuselessernonsensthere
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL for a unix socket
[include]
files = /etc/supervisor/conf.d/*.conf
[program:cron]
command=/usr/sbin/cron -f -L 15
process_name=cron
priority=1
numprocess=1
autostart=true
autorestart=true
[program:ganesha]
command=/usr/bin/ganesha.nfsd -f /etc/ganesha/ganesha.conf -L /var/log/ganesha/ganesha.log -F
process_name=ganesha
numprocs=1
priority=1
autostart=true
autorestart=true
[program:idmapd]
command=/usr/sbin/rpc.idmapd -f -v -c /etc/ganesha/idmapd.conf
directory=/
process_name=rpc.idmapd
numprocs=1
priority=1
autostart=true
autorestart=true
[program:statd]
command=/sbin/rpc.statd --no-notify --foreground --no-syslog
directory=/
process_name=rpc.statd
numprocs=1
priority=1
autostart=true
autorestart=true