An NFSv3 mount with locking makes the kernel register its lock manager
with rpcbind on the Docker host itself. On a host without nfs-common the
mount fails with a bare "connection refused" that reads like the NAS is
unreachable, even though the NAS and its ports are fine.
Only StackPilot ever writes the backup files on an NFS destination, one
writer per file, so nothing needs locking. mount_options() now adds
nolock unless the options already choose (lock, nolock, local_lock=) or
ask for NFSv4, which locks in-protocol and needs no rpcbind. Existing
destinations get their volume recreated once with the new options.
The refused-mount hint now points at rpcbind/nfs-common on the Docker
host first, and "protocol not supported" explains the missing kernel
module (modprobe nfsv4). The form labels the option.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
docker-py's containers.run(remove=True) only removes a container after it
ran. A helper whose start failed - an NFS destination refusing the mount -
stayed behind in "Created", holding the destination's volume. Every Test
click and scheduled backup added one (a user found 18), the volume could
not be deleted even with everything stopped, and changed mount options
never took effect because the volume could not be recreated with them.
- docker_client.run_container: create, start, wait, collect output and
always remove, also when the start fails; pulls a missing image. Used
for NFS destination I/O, bind-path inspection, the volume wipe before a
restore and the Trivy scanner.
- Every helper container is labelled stackpilot.helper; on startup the
stopped ones are removed, together with anything left on an NFS
destination volume by older versions.
- When an NFS destination's options change, stopped containers holding
its volume are removed before it is recreated; a running one is now a
clear error instead of silently keeping the old options.
- A refused mount explains itself: NFS enabled and reachable from the
Docker host, NFSv3 needing rpcbind and mountd, and nfsvers=4 needing
only port 2049.
- The scanner waits for Trivy's own 10-minute timeout instead of giving
up after the default client's 60 seconds.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>