Introduce DATA_DIR env var for consistent data path configuration

Replace separate DATABASE_URL and IMAGE_CACHE_DIR env vars with a
single DATA_DIR root (default: ./data, Docker: /data). DATABASE_URL
and CACHE_DIR are derived from it but can still be overridden
individually.

Also:
- Pin pnpm to @10 for reproducible Docker builds
- Add --link to COPY instructions for better BuildKit cache reuse
- Add syntax directive for BuildKit features
- Simplify Dockerfile mkdir to just /data (ensureImageDirs handles subdirs)
- Remove redundant DATABASE_URL from docker-compose.yml

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-03 14:26:08 -05:00
co-authored by Claude Opus 4.6
parent fb785645f8
commit ed6e061436
7 changed files with 28 additions and 20 deletions
+5 -7
View File
@@ -1,10 +1,10 @@
# ─── Database ───────────────────────────────────────────────────────────
# SQLite database URL (Docker: file:/data/sqlite.db, local dev: file:sqlite.db)
# DATABASE_URL=file:/data/sqlite.db
# ─── Data Directory ──────────────────────────────────────────────────────
# Root directory for SQLite database and image cache (default: ./data, Docker: /data)
# DATA_DIR=./data
# ─── TMDB (required) ───────────────────────────────────────────────────────
# API Read Access Token — get one at https://www.themoviedb.org/settings/api
TMDB_API_READ_ACCESS_TOKEN=your_tmdb_api_read_access_token_here
TMDB_API_READ_ACCESS_TOKEN=
# Optional: override TMDB base URLs (advanced)
# TMDB_API_BASE_URL=https://api.themoviedb.org/3
@@ -13,7 +13,7 @@ TMDB_API_READ_ACCESS_TOKEN=your_tmdb_api_read_access_token_here
# ─── Auth (required) ───────────────────────────────────────────────────────
# Random secret for session encryption (min 32 chars)
# Generate one with `npx @better-auth/cli secret` or `openssl rand -base64 32`
BETTER_AUTH_SECRET=your_secret_here
BETTER_AUTH_SECRET=
# Public URL of your instance, especially important if reverse proxy is used
BETTER_AUTH_URL=http://localhost:3000
@@ -28,7 +28,5 @@ BETTER_AUTH_URL=http://localhost:3000
# DISABLE_PASSWORD_LOGIN=false # Set to "true" to hide email/password form when OIDC is configured
# ─── Image Caching ─────────────────────────────────────────────────────
# Downloads TMDB images to local disk for faster serving (default: /data/images)
# IMAGE_CACHE_DIR=/data/images
# Set IMAGE_CACHE_ENABLED to "false" to use TMDB CDN directly (default: enabled)
# IMAGE_CACHE_ENABLED=true
+2 -2
View File
@@ -80,7 +80,7 @@ All app tables use UUID text primary keys generated via the `uuid` package. Bett
Only paths are stored in DB. Image URLs are resolved **server-side only** — API routes and server components call `tmdbImageUrl()` from `lib/tmdb/image.ts` before sending data to clients. Client components never import `tmdbImageUrl`; they receive ready-to-use URLs.
When `IMAGE_CACHE_ENABLED` is set (default), images are downloaded to local disk (`IMAGE_CACHE_DIR`, defaults to `/data/images`) and served via `app/api/images/[...path]/route.ts`. Categories: `posters` (w500), `backdrops` (w1280), `stills` (w1280), `logos` (w92). When disabled, `tmdbImageUrl()` returns direct TMDB CDN URLs using `TMDB_IMAGE_BASE_URL` (defaults to `https://image.tmdb.org/t/p`).
When `IMAGE_CACHE_ENABLED` is set (default), images are downloaded to local disk (`CACHE_DIR`, derived from `DATA_DIR/images`) and served via `app/api/images/[...path]/route.ts`. Categories: `posters` (w500), `backdrops` (w1280), `stills` (w1280), `logos` (w92). When disabled, `tmdbImageUrl()` returns direct TMDB CDN URLs using `TMDB_IMAGE_BASE_URL` (defaults to `https://image.tmdb.org/t/p`).
Core files: `lib/services/image-cache.ts` (caching logic), `lib/tmdb/image.ts` (URL construction), `app/api/images/[...path]/route.ts` (proxy route).
@@ -90,7 +90,7 @@ Registered in `lib/jobs/registry.ts`, started via Next.js instrumentation hook (
### Environment variables
See `.env.example`: `DATABASE_URL`, `TMDB_API_READ_ACCESS_TOKEN`, `BETTER_AUTH_SECRET`, `BETTER_AUTH_URL`.
See `.env.example`: `DATA_DIR` (root for DB + cache, default `./data`), `TMDB_API_READ_ACCESS_TOKEN`, `BETTER_AUTH_SECRET`, `BETTER_AUTH_URL`. `DATABASE_URL` and `CACHE_DIR` are derived from `DATA_DIR` but can be overridden individually.
## Browser Automation
+8 -6
View File
@@ -1,5 +1,6 @@
# syntax=docker/dockerfile:1
FROM node:24-alpine AS base
RUN corepack enable && corepack prepare pnpm@latest --activate
RUN corepack enable && corepack prepare pnpm@10 --activate
# --- Dependencies ---
FROM base AS deps
@@ -25,16 +26,17 @@ WORKDIR /app
ENV NODE_ENV=production
ENV HOSTNAME=0.0.0.0
ENV PORT=3000
ENV DATA_DIR=/data
RUN addgroup --system --gid 1001 nodejs \
&& adduser --system --uid 1001 nextjs \
&& mkdir -p /data/images/posters /data/images/backdrops /data/images/stills /data/images/logos \
&& mkdir -p /data \
&& chown -R nextjs:nodejs /data
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder --chown=nextjs:nodejs /app/drizzle ./drizzle
COPY --from=builder --link --chown=nextjs:nodejs /app/public ./public
COPY --from=builder --link --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --link --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder --link --chown=nextjs:nodejs /app/drizzle ./drizzle
USER nextjs
EXPOSE 3000
+1 -2
View File
@@ -1,6 +1,6 @@
services:
sofa:
image: ghcr.io/jakejarvis/sofa:latest
image: ghcr.io/jakejarvis/sofa:edge
container_name: sofa
restart: unless-stopped
ports:
@@ -8,7 +8,6 @@ services:
volumes:
- sofa-data:/data
environment:
- DATABASE_URL=file:/data/sqlite.db
- TMDB_API_READ_ACCESS_TOKEN=${TMDB_API_READ_ACCESS_TOKEN}
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET}
- BETTER_AUTH_URL=${BETTER_AUTH_URL:-http://localhost:3000}
+4 -1
View File
@@ -1,10 +1,13 @@
import { defineConfig } from "drizzle-kit";
const DATA_DIR = process.env.DATA_DIR || "./data";
const DATABASE_URL = process.env.DATABASE_URL || `file:${DATA_DIR}/sqlite.db`;
export default defineConfig({
dialect: "turso",
schema: "./lib/db/schema.ts",
out: "./drizzle",
dbCredentials: {
url: process.env.DATABASE_URL ?? "file:./data/sqlite.db",
url: DATABASE_URL,
},
});
+4 -1
View File
@@ -7,10 +7,13 @@ const globalForDb = globalThis as unknown as {
_client: ReturnType<typeof createClient> | undefined;
};
const DATA_DIR = process.env.DATA_DIR || "./data";
const DATABASE_URL = process.env.DATABASE_URL || `file:${DATA_DIR}/sqlite.db`;
function getClient() {
if (!globalForDb._client) {
globalForDb._client = createClient({
url: process.env.DATABASE_URL ?? "file:./data/sqlite.db",
url: DATABASE_URL,
});
globalForDb._client.execute("PRAGMA journal_mode = WAL");
globalForDb._client.execute("PRAGMA foreign_keys = ON");
+4 -1
View File
@@ -17,7 +17,10 @@ const CATEGORY_SIZES: Record<ImageCategory, string> = {
const IMAGE_BASE_URL =
process.env.TMDB_IMAGE_BASE_URL || "https://image.tmdb.org/t/p";
const CACHE_DIR = process.env.IMAGE_CACHE_DIR || "/data/images";
const DATA_DIR = process.env.DATA_DIR || "./data";
const CACHE_DIR = process.env.CACHE_DIR
? path.join(process.env.CACHE_DIR, "images")
: path.join(DATA_DIR, "images");
export function imageCacheEnabled(): boolean {
return process.env.IMAGE_CACHE_ENABLED !== "false";