This post explains how I run samba and avahi-daemon inside a docker container to act as a time-machine server for all the Mac computers on my home network.
Dockerfile
I used ubuntu:focal
as a base image and created the following Dockerfile:
FROM ubuntu:focal
#Install samba and avahi-daemon inside the image
RUN apt update && apt install samba avahi-daemon -y
#Expose the ports which are needed for samba and avahi-daemon
EXPOSE 137/udp 138/udp 139 445 5353/udp
#Copy config files into the image
COPY smb.conf /etc/samba/smb.conf
COPY samba.service /etc/avahi/services/samba.service
#Copy a run.sh script which will run samba and avahi-daemon when the container is invoked
COPY run.sh /
#Configure the avahi-daemon to run without dbus
RUN sed -i 's/.*enable-dbus=.*/enable-dbus=no/' /etc/avahi/avahi-daemon.conf
#Make the run.sh script executable
RUN chmod +x /run.sh
#Change the hostname in the avahi-hostname config file
RUN sed -i s/^#host-name=foo/host-name=time-machine-docker/ /etc/avahi/avahi-daemon.conf
CMD ["/bin/bash","/run.sh"]
In order to build the image based on the Dockerfile, the following command is executed:
docker build -t time-machine-docker .
The dockerfile is stored in a directory (on the docker host) called ~/docker/docker-time-machine along with the other required files:
- invoke.sh
- run.sh
- samba.service
- smb.conf
run.sh script
The run.sh
script is executed when an instance of the container is created. It contains the following commands to setup the samba user
#!/bin/bash
SAMBA_USER="tm-user"
SAMBA_PASSWORD="passwordgoeshere"
useradd -M $SAMBA_USER
echo -e "${SAMBA_PASSWORD}\n${SAMBA_PASSWORD}" | smbpasswd -sa "$SAMBA_USER"
# Start Avahi daemon
avahi-daemon --daemonize --no-drop-root
# Start Samba daemon
smbd --foreground --no-process-group
TODO: Make better use of environment variables so that the username and password for the samba user can be defined when the container is instantiated.
Avahi Service File (Samba)
<?xml version="1.0" standalone='no'?>
<!DOCTYPE service-group SYSTEM "avahi-service.dtd">
<service-group>
<name replace-wildcards="yes">%h</name>
<service>
<type>_smb._tcp</type>
<port>445</port>
</service>
<service>
<type>_device-info._tcp</type>
<port>0</port>
<txt-record>model=TimeCapsule8,119</txt-record>
</service>
<service>
<type>_adisk._tcp</type>
<txt-record>dk0=adVN=time-machine-docker,adVF=0x82</txt-record>
<txt-record>sys=waMa=0,adVF=0x100</txt-record>
</service>
</service-group>
Samba configuration file
The smb.conf file looks like this:
[global]
security = user
# mac support
spotlight = yes
vfs objects = acl_xattr catia fruit streams_xattr
fruit:aapl = yes
fruit:time machine = yes
fruit:nfs_aces = no
fruit:copyfile = no
fruit:model = MacSamba
inherit permissions = yes
workgroup = WORKGROUP
multicast dns register = no
server string = %h server (Samba, Ubuntu)
client max protocol = default
client min protocol = SMB2_02
server max protocol = SMB3
server min protocol = SMB2_02
# use a separate log file for each machine that connects
log file = /var/log/samba/log.%m
# Cap the size of the individual log files (in KiB).
max log size = 1000
# We want Samba to only log to /var/log/samba/log.{smbd,nmbd}.
# Append syslog@1 if you want important messages to be sent to syslog too.
logging = file
# Do something sensible when Samba crashes: mail the admin a backtrace
panic action = /usr/share/samba/panic-action %d
[time-machine-docker]
# Load in modules (order is critical!)
vfs objects = catia fruit streams_xattr
fruit:time machine = yes
comment = Time Machine Backup
path = /time-machine-docker
available = yes
valid users = tm-user
browseable = yes
guest ok = no
writable = yes
hosts allow = 192.168.0.0/24
hosts deny = ALL
Invoke Script
With all of the above in place, the invoke.sh
script can be used to create and run an instance of the container.
#!/bin/bash
docker run -d \
--net=host \
--name time-machine-docker \
--restart unless-stopped \
--hostname time-machine-docker \
-e ADVERTISED_HOSTNAME="time-machine-docker" \
-v /mnt/data/time-machine:/time-machine-docker \
time-machine-docker
NOTE: You’ll notice that I am using host networking. This is the easiest way to make sure that the avahi-daemon will be reachable accross the wider network. If the container was run in bridge mode, another avahi-daemon acting as a “reflector” would be required on the host machine