fix: bind /nix to /mnt/cache, NOT docker-managed volume

A docker-managed named volume lives at /var/lib/docker/volumes/,
which is INSIDE docker.img (a 200 GB loop file shared with all
images, container layers, and every other docker volume on the
host). The Plutarch + haskell-nix closure for Liqwid-Labs/agora
is tens of GB.

Running nix develop against agora ONCE was enough to fill docker.img
to 100% (196/200 GB used, 2 GB free). Every container on Lucy was
about to start failing writes. Recovery: kill nix process, docker
compose down, free 66 GB of BuildKit cache via `docker builder
prune -a`, switch /nix to /mnt/cache bind mount (88+ GB free on
that pool, completely separate from docker.img).

Bind mount caveat: bare bind to an empty host dir shadows the
image's /nix install (the previous bug we caught with the
named-volume fix). One-time seed required:
  mkdir -p /mnt/cache/appdata/crafting-table/nix
  chown 1000:1000 /mnt/cache/appdata/crafting-table/nix
  docker create --name ct-seed crafting-table:local
  docker cp ct-seed:/nix/. /mnt/cache/appdata/crafting-table/nix/
  docker rm ct-seed

After seed, the bind mount works because the host path has the
nix tree already populated. Subsequent docker compose up -d picks
up the populated /nix and `nix --version` works in-container.
This commit is contained in:
Kayos 2026-05-06 22:55:17 -07:00
parent 605844f5be
commit 8b1774130b

View file

@ -35,19 +35,32 @@ services:
- /mnt/user/appdata/crafting-table/data:/data - /mnt/user/appdata/crafting-table/data:/data
- /mnt/user/appdata/crafting-table/workspace:/workspace - /mnt/user/appdata/crafting-table/workspace:/workspace
- /mnt/user/appdata/crafting-table/caches:/caches - /mnt/user/appdata/crafting-table/caches:/caches
# Nix store — Docker-managed volume (NOT bind mount) so the image's # Nix store — bind mount to /mnt/cache (88+ GB free) NOT a bare
# pre-installed /nix tree gets seeded into the volume on first up. # docker-managed volume. A docker-managed volume lives at
# A bare bind mount to an empty host dir would shadow the image's # /var/lib/docker/volumes/, which is INSIDE the docker.img loop
# /nix and leave `nix` unfindable inside the container. # file (200 GB allocated, shared with all images + container layers
# Persists haskell-nix downloads (multi-GB Plutarch / IOG flake # + every other volume). The Plutarch + haskell-nix closure is
# closures) across container rebuilds. # tens of GB; running nix develop against Liqwid-Labs/agora once
- crafting-table-nix:/nix # was enough to fill docker.img to 100% and break every container
# on Lucy. Caught 2026-05-06 mid-build.
#
# Bind to /mnt/cache so Plutarch + haskell-nix closures live on
# the cache pool, not docker.img. Pre-seed the host path with the
# image's stock /nix install ONCE at container init (or by
# `docker create + docker cp` when the path is empty) — bare bind
# mount to an empty host dir shadows the image's /nix install.
#
# If the bind path is missing or empty when you `docker compose
# up -d` for the first time, do this once:
# mkdir -p /mnt/cache/appdata/crafting-table/nix
# chown 1000:1000 /mnt/cache/appdata/crafting-table/nix
# docker create --name ct-seed crafting-table:local
# docker cp ct-seed:/nix/. /mnt/cache/appdata/crafting-table/nix/
# docker rm ct-seed
- /mnt/cache/appdata/crafting-table/nix:/nix
networks: [sulkta] networks: [sulkta]
restart: unless-stopped restart: unless-stopped
volumes:
crafting-table-nix:
networks: networks:
sulkta: sulkta:
external: true external: true