diff --git a/README.md b/README.md index 850b292..0354cfb 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,16 @@ That directory must exist and be owned by the user compose runs as — `make up` creates it, and `FOODSTER_UID`/`FOODSTER_GID` in `.env` tell the container who that is. Get them from `id -u` and `id -g`. +If the app exits with `cannot open /data/foodster.db ... unable to open +database file (14)`, the ownership does not match. Docker creates a missing +bind-mount directory as root, and the container is not root: + +```sh +ls -ldn data # whose is it? +sudo chown -R 1000:1000 data # match FOODSTER_UID / FOODSTER_GID +docker compose restart +``` + ## Security Access is a single shared password over HTTP Basic — no accounts, no diff --git a/cmd/foodster/main.go b/cmd/foodster/main.go index 59f47e5..2b7a323 100644 --- a/cmd/foodster/main.go +++ b/cmd/foodster/main.go @@ -128,6 +128,17 @@ func openDB(path string) (*sql.DB, error) { // sidesteps SQLITE_BUSY entirely. Raise it if reads ever contend. db.SetMaxOpenConns(1) + // sql.Open is lazy, so without this the first failure surfaces from + // whatever query ran first and says nothing useful. The usual cause is a + // bind-mounted directory owned by a different user than the container + // runs as, so name the path and the uid. + if err := db.Ping(); err != nil { + db.Close() + return nil, fmt.Errorf( + "cannot open %s as uid %d gid %d: %w (is that directory writable by this user?)", + path, os.Getuid(), os.Getgid(), err) + } + if err := migrate(db); err != nil { db.Close() return nil, err