LiteFS exists, but Drizzle can't find database

I try to figure out why drizzle can not connect to the database file. When I ssh into the container I can see that the file is in place. Drizzle migrations found it with the same env variable and migrations have no issues. I tried also file:/litefs/sqlite.db. The environment variable is set with secrets.

INFO  Starting clean up
INFO  Umounting /dev/vdc from /data
WARN  could not unmount /rootfs: EINVAL: Invalid argument
[92.989536] reboot: Restarting system

Firecracker v1.12.1 starting
Listening on API socket "/fc.sock"

INFO  Starting init (commit: a570442)...
INFO  Checking filesystem on /data
/dev/vdc: clean, 19/64512 files, 8817/258048 blocks
INFO  Mounting /dev/vdc at /data (uid=0, gid=0, chmod=0755)
INFO  Resized /data to 1056964608 bytes
INFO  Preparing to run: `litefs mount` as root
INFO  [fly api proxy] listening at /.fly/api
LiteFS v0.5.14, commit=a51e72d...
config file read from /etc/litefs.yml
INFO  host environment detected (fly.io)
INFO  no backup client configured, skipping
INFO  Using Consul to determine primary
INFO  initializing consul
INFO  wal-sync: no wal file exists on "sqlite.db", skipping sync
INFO  using existing cluster id: LFSC38943094791DD683
INFO  LiteFS mounted to: /litefs
INFO  http server listening on: http://localhost:20202
INFO  waiting to connect to cluster
INFO  primary lease acquired, advertising as http://6839146a7d9008.vm.leihzeit.internal:20202
INFO  connected to cluster, ready
INFO  node is a candidate, automatically promoting to primary
INFO  node is already primary, skipping promotion
INFO  proxy server listening on: http://localhost:3000
INFO  executing command: npx drizzle-kit migrate
INFO  SSH listening on [fdaa:a:2efc:a7b:479:50ad:c758:2]:22

INFO  Reading config file '/app/drizzle.config.ts'
INFO  No config path provided, using default
[✓] migrations applied successfully
INFO  starting background subprocess: npm [start]
waiting for signal or subprocess to exit

> start
> react-router-serve ./build/server/index.js

DATABASE_URL file:///litefs/sqlite.db
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
/app/node_modules/better-sqlite3/lib/database.js:65
throw new TypeError('Cannot open database because the directory does not exist');
TypeError: Cannot open database because the directory does not exist
    at new Database (/app/node_modules/better-sqlite3/lib/database.js:65:9)
    at drizzle (file:///app/node_modules/src/better-sqlite3/driver.ts:94:61)
    at file:///app/build/server/assets/server-build-CcpnzTgH.js:220:12
    at ModuleJob.run (node:internal/modules/esm/module_job:274:25)
    at onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:644:26)
    at run (/app/node_modules/@react-router/serve/dist/cli.js:80:15)

subprocess exited with error code 1, litefs shutting down
INFO  exiting primary, destroying lease
litefs shut down complete
INFO  Main child exited normally with code: 1

I also logged DATABASE_URL in the server side file and the file path looks good.

My Dockerfile:

FROM node:22-alpine AS development-dependencies-env
WORKDIR /app
COPY . .
RUN npm ci

FROM node:22-alpine AS production-dependencies-env
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev

FROM node:22-alpine AS build-env
WORKDIR /app
COPY . .
COPY --from=development-dependencies-env /app/node_modules /app/node_modules
RUN npm run build

COPY drizzle ./drizzle
COPY drizzle.config.ts ./drizzle.config.ts

# Stage 4: Final runtime with LiteFS
FROM alpine AS runtime
WORKDIR /app
ARG LITEFS_CONFIG=litefs.yml

# Install runtime dependencies
RUN apk add --no-cache nodejs npm bash fuse3 sqlite ca-certificates curl

# Copy LiteFS binary
COPY --from=flyio/litefs:0.5 /usr/local/bin/litefs /usr/local/bin/litefs

# Copy production node_modules + built app + generated Drizzle client
COPY package.json package-lock.json ./ 
COPY drizzle.config.ts ./drizzle.config.ts
COPY --from=production-dependencies-env /app/node_modules /app/node_modules
COPY --from=build-env /app/build /app/build
COPY --from=build-env /app/drizzle /app/drizzle

# Copy LiteFS config
COPY litefs.yml /etc/litefs.yml

# Set LiteFS as the entrypoint
# It will mount the filesystem, then exec ["npm","run","start"] as defined in litefs.yml
ENTRYPOINT ["litefs", "mount"]

My litefs.yml

fuse:
  # Required. This is the mount directory that applications will
  # use to access their SQLite databases.
  dir: "/litefs"

data:
  # Path to internal data storage.
  dir: "/data/litefs"

proxy:
  # matches the internal_port in fly.toml
  addr: ":3000"
  target: "localhost:3001"
  db: "sqlite.db"

# The lease section specifies how the cluster will be managed. We're using the
# "consul" lease type so that our application can dynamically change the primary.
#
# These environment variables will be available in your Fly.io application.
lease:
  type: "consul"
  candidate: ${FLY_REGION == PRIMARY_REGION}
  promote: true
  advertise-url: "http://${HOSTNAME}.vm.${FLY_APP_NAME}.internal:20202"

  consul:
    url: "${FLY_CONSUL_URL}"
    key: "litefs/${FLY_APP_NAME}"

exec:
  - cmd: npx drizzle-kit migrate
    if-candidate: true

  - cmd: npm start

I think it’s b/c the Fly volumes don’t get mounted at build time, hence why the file doesn’t exist.

My initial guess is that this is more of a runtime issue, since logging process.env shows the correct path and the container builds successfully.

I suspect that the LiteFS commands write directly to the volume, so the migration completes fine. However, when the start command runs, the volume might not be mounted quickly enough for the server to access the file. I plan to add a loop in the litefs.yml commands to wait until the file is available before proceeding.

Ah you’re right. I got confused on the “build” here.

I added RUN mkdir -p /litefs in the dockerfile to bypass the missing folder in this way and tried - cmd: sh -c 'while [ ! -d /litefs ]; do sleep 0.1; done' in litefs.yml but still no luck.

Turns out the database url should not be set as canonical uri path.
Drizzle wants to have an absolute path without file:prefix.

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.