Skip to content

Docker quick-start crash-loops: named volume /data is root-owned but container runs as uid 1654 #140

Description

@Aaronontheweb

Summary

The documented Docker quick-start crashes on first start. docker/docker-compose.yml mounts a named volume at /data, but the image runs as the non-root user uid=1654 (app) while Docker initializes the named volume mount point as root:root (0755). The app can't create the SQLite database file, so the container exits during schema migration and crash-loops.

This makes the README's primary "Quick Start → Docker" path fail out of the box.

Repro

Against ghcr.io/netclaw-dev/skillserver:0.4.0 and the shipped compose file:

docker compose -f docker/docker-compose.yml up -d
docker compose -f docker/docker-compose.yml logs

Observed

info: SkillServer.Data.DatabaseInitializer[0]
      Starting database schema migration...
Unhandled exception. Microsoft.Data.Sqlite.SqliteException (0x80004005): SQLite Error 14: 'unable to open database file'.
   at SkillServer.Data.SchemaMigrator.MigrateAsync(...)
   at SkillServer.Data.DatabaseInitializer.InitializeAsync(...)
   at Program.<Main>$(String[] args) in .../src/SkillServer/Program.cs:line 41

When SKILLSERVER__DATAPATH is left at its default, the same failure surfaces as UnauthorizedAccessException: Access to the path '/app/data' is denied/app is also root-owned and not writable by uid 1654.

Root cause

  • Image runs as uid=1654 (app) (APP_UID=1654, .NET SDK container defaults to non-root).
  • The named Docker volume's mount point is created root:root 0755 because /data does not exist in the image, so Docker has no owned directory to inherit from.
  • DatabaseInitializerDirectory.CreateDirectory / SQLite open fails: the app user can't write inside a root-owned directory.

Confirmed directly:

$ docker run --rm --entrypoint sh -v <vol>:/data ghcr.io/netclaw-dev/skillserver:0.4.0 -c 'id; ls -ld /data'
uid=1654(app) gid=1654(app) groups=1654(app)
drwxr-xr-x 2 root root 4096 /data

Verified workaround (for users, until this is fixed)

Run as the host user against a bind mount the host owns:

docker run -d -p 8080:8080 \
  --user "$(id -u):$(id -g)" \
  -v ./data:/data \
  -e SKILLSERVER__DATAPATH=/data \
  -e SKILLSERVER__APIKEY=<key> \
  ghcr.io/netclaw-dev/skillserver:0.4.0

Reaches healthy in ~2s and persists the SQLite DB in ./data.

Suggested fix

Make the shipped docker compose ... up work out of the box while keeping the app non-root. Candidate approaches:

  1. Ensure the image ships a writable /data owned by 1654:1654 so the named volume inherits that ownership on first init (needs a Dockerfile step or an SDK-container mechanism to create + chown the dir).
  2. Or fix docker/docker-compose.yml: a small init step that chowns the volume to 1654:1654 before the server starts (the image has sh + chown), or switch to a bind mount plus a documented --user.

Whichever approach lands, acceptance is: from a clean state (docker compose ... down -v), docker compose -f docker/docker-compose.yml up -d reaches healthy, runs as non-root, and persists data across restarts.

Environment

  • Image: ghcr.io/netclaw-dev/skillserver:0.4.0 (sha256:049c3f19d10deac5e74f4d7d4c3321b7f783b266f09f55df66c22b18c1769445)
  • Docker 29.2.1, Linux

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions