OK, this is actually about Docker and disk space, but close enough. While trying to download ultra high definition Linux ISOs, I encountered an error from Transmission, running in Docker: “No space left on device.” Odd, considering I was pretty sure I had a few TB free. Let’s see if the logs have anything useful to say (also, yes, I checked around the lines in question to see if there was other information):

❯ docker logs transmission-LINUX_ISOs 2>&1 | grep space
[2020-07-07 20:58:16.730] Couldn't open "/downloads/incomplete/LINUX_ISO.part": No space left on device (/home/buildozer/aports/community/transmission/src/transmission-3.00/libtransmission/fdlimit.c:195)
[2020-07-07 20:58:16.730] LINUX_ISO tr_fdFileCheckout failed for "/downloads/incomplete/LINUX_ISO.part": No space left on device (/home/buildozer/aports/community/transmission/src/transmission-3.00/libtransmission/inout.c:95)
[2020-07-07 20:58:16.730] LINUX_ISO No space left on device (/downloads/4K/LINUX_ISO) (/home/buildozer/aports/community/transmission/src/transmission-3.00/libtransmission/torrent.c:574)

I followed the source code listed, and found that it’s just trying to allocate file descriptors, which makes sense. OK, I was pretty sure I had available space, but let’s check.

❯ dfd
# alias dfd="df -h | awk '/1:2:3:4|data|Filesystem/'"
Filesystem      Size  Used Avail Use% Mounted on
1:2:3:4          29T   25T  4.1T  86% /media/merged
/dev/sdf1       7.1T  6.7T  455G  94% /media/data2
/dev/sde1       7.2T  6.6T  636G  92% /media/data1
/dev/sdh1       7.3T  6.4T  905G  88% /media/data4
/dev/sdg1       7.3T  4.8T  2.1T  70% /media/data3

OK, how about inodes?

❯ df -hi
Filesystem     Inodes IUsed IFree IUse% Mounted on
udev             2.0M   504  2.0M    1% /dev
tmpfs            2.0M  1.1K  2.0M    1% /run
/dev/md0          14M  890K   14M    7% /
tmpfs            2.0M     1  2.0M    1% /dev/shm
tmpfs            2.0M     3  2.0M    1% /run/lock
tmpfs            2.0M    17  2.0M    1% /sys/fs/cgroup
1:2:3:4          1.4G   94K  1.4G    1% /media/merged
/dev/sdf1        462M  5.8K  462M    1% /media/data2
/dev/sde1        466M   42K  466M    1% /media/data1
/dev/sdc1        292M    12  292M    1% /media/par1
/dev/sdh1        233M   36K  233M    1% /media/data4
/dev/sdg1        233M   12K  233M    1% /media/data3
/dev/sdb1        292M    12  292M    1% /media/par2
tmpfs            2.0M    11  2.0M    1% /run/user/1000

Wait, what if the docker daemon is out of room?

❯ df -h /var/lib/docker
Filesystem      Size  Used Avail Use% Mounted on
/dev/md0        218G   32G  176G  16% /

Guess I can try restarting the container… nope, that didn’t fix it either.

I found some articles discussing container limits with earlier versions of Docker, but I’m using the latest, with overlay2, and besides, the container’s size isn’t my issue - I think.

Let’s check volume mounts; maybe there are a bunch of stuck torrents cluttering up /incomplete or something.

❯ docker inspect transmission-LINUX_ISOS | jq -r '.[].Mounts'
[
  {
    "Type": "bind",
    "Source": "/$HOME/.transmission-LINUX_ISOS",
    "Destination": "/config",
    "Mode": "",
    "RW": true,
    "Propagation": "rprivate"
  },
  {
    "Type": "bind",
    "Source": "/media/merged/LINUX_ISOS",
    "Destination": "/downloads",
    "Mode": "",
    "RW": true,
    "Propagation": "rprivate"
  },
  {
    "Type": "bind",
    "Source": "/media/merged/LINUX_ISOS/incomplete",
    "Destination": "/incomplete",
    "Mode": "",
    "RW": true,
    "Propagation": "rprivate"
  },
  {
    "Type": "bind",
    "Source": "/media/merged/LINUX_ISOS/torrents",
    "Destination": "/watch",
    "Mode": "",
    "RW": true,
    "Propagation": "rprivate"
  }
]
# Yes, you could just ls the volume mount as well
❯ docker exec -it transmission-LINUX_ISOS /bin/bash
root@1fa057d96dcf:/# ls /incomplete

I got as far as joining LinuxServer.io’s Discord and typing out my symptoms and what I had done, when, as typing often does, I hit upon a thought: I’m using mergerfs to join my data disks. I have minfreespace quotas set up for the data disks. What if one of those was at or near the minimum?

❯ tail -1 /etc/fstab
/media/data* /media/merged fuse.mergerfs defaults,nonempty,allow_other,use_ino,minfreespace=500G 0 0

What did my initial df show again?

/dev/sdf1       7.1T  6.7T  455G  94% /media/data2

Ah ha. Now, what’s interesting is that even at a second glance, there shouldn’t be an issue. I have a JBOD of 4x data disks under mergerfs, and for this particular file, it was being saved to /media/merged/movies/4K/ (I may as well drop the charade of Linux ISOs). That particular path only exists on /media/data1, which showed as 636G free. However, /media/merged/movies/incomplete, the temporary location for torrents, only exists on - you guessed it - /media/data2. Thus, ENOSPC was returned, and the transfer stopped. Amusingly, mergerfs’ README covers this issue - that’ll teach me to not turn to documentation earlier.

The short-term fix is to rebalance, with mergerfs.balance, which runs rsync to distribute files around more evenly. The medium-term fix is to add moveonenospc to my mount options. The long-term fix is to move off of mergerfs, because while it and SnapRAID are great tools for what they are, they have shortcomings, and raise some annoyances.