Skip to content

Add a Docker-based development environment - #858

Open
krejko wants to merge 1 commit into
Listenarrs:canaryfrom
nexalapp:chore/docker-dev-environment
Open

Add a Docker-based development environment#858
krejko wants to merge 1 commit into
Listenarrs:canaryfrom
nexalapp:chore/docker-dev-environment

Conversation

@krejko

@krejko krejko commented Aug 20, 2026

Copy link
Copy Markdown

Optional tooling — close this if it isn't a direction you want. This adds a second way to run the project, which is a maintenance surface you may not want to own. Nothing existing changes, so rejecting it costs nothing.

Summary

Adds an optional containerised dev environment:

docker compose -f docker-compose.dev.yml up -d

Source is bind-mounted rather than copied, with dotnet watch and vite running inside the container, so hot reload behaves as it does natively.

Why this is worth having

macOS filesystem scanning does not currently work — and that's not hypothetical. UnixOpenFlags.EnsureMacOSArchitectureSupported rejects anything but x64, and on x64 the raw fstat P/Invoke misclassifies every regular file (see #855). Until both are resolved, a macOS contributor cannot exercise the scan path at all. Running Linux in a container sidesteps the entire class of problem, on the platform CI actually builds and tests.

It also removes the .NET SDK and Node version-matching step from onboarding, and gives the container a working ffprobe from apt rather than the runtime downloader (which is itself broken on macOS — see #854).

Changes

Added

  • Dockerfile.dev — SDK 10 + Node 24 + ffmpeg. Nothing is COPYed in.
  • docker-compose.dev.yml — bind-mounted source, published ports 4545/5173.
  • docker/dev-entrypoint.sh — installs deps, then runs both servers under concurrently.

Design notes

One container, not two. Both servers share a container so vite's proxy to http://localhost:4545 works unchanged. Splitting them would mean editing vite.config.ts to target a service name — a source change to support tooling, which seemed like the wrong trade.

Masking volumes. Named volumes cover every obj/ and bin/ directory and both node_modules trees. Host build output is targeted at the host RID (osx-arm64, osx-x64, …) and would otherwise be read by the Linux build; node_modules may hold platform-native binaries. A separate volume backs .env so the container keeps its own database and never competes with a native instance.

Entrypoint in a file. The concurrently invocation needs nested quoting that YAML folding mangles — the script avoids that.

Polling watchers. DOTNET_USE_POLLING_FILE_WATCHER=1, since FSEvents/inotify don't propagate through bind mounts on macOS.

Testing

Verified on macOS (Apple Silicon, Docker Desktop, VirtioFS), scanning a real 11-book library:

status = Completed | items = 11

Native arm64 Linux, so no emulation: restore 5.8s, incremental build ~16s, ffprobe at /usr/bin/ffprobe.

Storage capability check on the bind mount, since the scanner depends on it:

statx INO/BTIME/MNT_ID name_to_handle_at FS_IOC_GETVERSION
VirtioFS bind mount ✓ (0x1fff) ✗ EOPNOTSUPP ✗ ENOTTY
Named volume (ext4)

Bind mounts land in path-only mode — the existing HasDurableGenerationProof branch handles this and scanning works. Contributors wanting full generation proof can point the library mount at a named volume.

Notes

Both files are new rather than edits to the production Dockerfile, so there's no merge risk for release builds. The library mount is parameterised via LISTENARR_DEV_LIBRARY (defaults to ./dev-library).

Not wired into CI — purely local tooling.

🤖 Generated with Claude Code

Adds Dockerfile.dev, docker-compose.dev.yml and docker/dev-entrypoint.sh, so a
contributor can run the stack with:

    docker compose -f docker-compose.dev.yml up -d

Source is bind-mounted rather than copied, and dotnet watch plus vite run inside
the container, so hot reload behaves as it does natively.

Both servers share one container so vite's proxy to http://localhost:4545 keeps
working unchanged; splitting them would require editing vite.config.ts to point
at a service name.

Named volumes mask every obj/ and bin/ directory and both node_modules trees.
Host build output is targeted at the host RID (osx-arm64, osx-x64, ...) and
would otherwise be read by the Linux build, and node_modules may contain
platform-native binaries. A separate volume backs .env so the container keeps
its own database and never competes with a native instance.

The image installs ffmpeg from apt, which gives the container a working ffprobe
without relying on the runtime downloader.

Both files are new rather than edits to the production Dockerfile, so this
carries no merge risk for release builds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@krejko
krejko requested a review from a team August 20, 2026 21:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant