From 1d8d8143f2110322e61e2576a4b03fe2366310ec Mon Sep 17 00:00:00 2001 From: Honey Date: Wed, 2 Sep 2026 08:03:14 +0200 Subject: [PATCH] refactor(docker): make image-based the default compose, build variant is .dev Less confusion: docker-compose.yml is now the default (pulls pre-built images) and the build-on-host variant is docker-compose.dev.yml. Updated all references (CLAUDE.md, README, .env.example, build-push-images.mjs, compose header). Co-Authored-By: Claude Opus 4.8 --- .env.example | 4 +- CLAUDE.md | 15 +++---- README.md | 7 +++- docker-compose.dev.yml | 75 +++++++++++++++++++++++++++++++++++ docker-compose.prod.yml | 63 ----------------------------- docker-compose.yml | 69 ++++++++++++++------------------ scripts/build-push-images.mjs | 4 +- 7 files changed, 122 insertions(+), 115 deletions(-) create mode 100644 docker-compose.dev.yml delete mode 100644 docker-compose.prod.yml diff --git a/.env.example b/.env.example index de85879..43da839 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,5 @@ # Copy to .env next to your docker-compose file and fill in. cp .env.example .env -# Used by docker-compose.yml (build-on-host) AND docker-compose.prod.yml (pulls images). +# Used by docker-compose.yml (pulls pre-built images) AND docker-compose.dev.yml (build-on-host). # ── URLs pre-filled on the frontend setup screen ───────────────────────────── NAVIDROME_URL=https://music.example.com @@ -22,7 +22,7 @@ GITEA_REPO=Docker/NovaSonic GITEA_TOKEN= # read token (only needed for a private repo) GITEA_APK_TAG=apk-latest -# ── Pre-built images (docker-compose.prod.yml only) ────────────────────────── +# ── Pre-built images (docker-compose.yml only) ─────────────────────────────── # REGISTRY = where `npm run push-images` pushed to and where the host pulls from. # Gitea container registry (lowercase owner): gitea.honeyy.duckdns.org/docker # Docker Hub: docker.io/youruser diff --git a/CLAUDE.md b/CLAUDE.md index a60dcd2..530f44d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -41,13 +41,14 @@ npm run build # production build → dist/ npm run preview # preview the production build ``` -Backend runs in Docker. Two ways: -- **`docker-compose.yml`** — builds on the host (mounts `./backend`, installs deps at container - start). Restart after backend changes: `docker compose restart novasonic-backend` (re-pulls yt-dlp). -- **`docker-compose.prod.yml`** — PULLS pre-built images (deps baked into `backend/Dockerfile` + - `scrobble/Dockerfile`), so restarts are instant. Build+push from dev with `npm run push-images` - (needs `REGISTRY`/`TAG` in `.env`), then on the host `docker compose -f docker-compose.prod.yml pull - && … up -d`. See `.env.example`. +Backend runs in Docker. Two compose files: +- **`docker-compose.yml`** (default/production) — PULLS pre-built images (deps baked into + `backend/Dockerfile` + `scrobble/Dockerfile`), so restarts are instant. Build+push from dev with + `npm run push-images` (needs `REGISTRY`/`TAG` in `.env`), then on the host `docker compose pull && + docker compose up -d`. See `.env.example`. +- **`docker-compose.dev.yml`** — builds on the host (mounts `./backend`, installs deps at container + start). Restart after backend changes: `docker compose -f docker-compose.dev.yml restart + novasonic-backend` (re-pulls yt-dlp). On the deploy host the music lives at `/opt/navidrome/music`. diff --git a/README.md b/README.md index d5b5407..a3729b1 100644 --- a/README.md +++ b/README.md @@ -122,10 +122,15 @@ TICKETMASTER_API_KEY= # optional — free key from developer.ticketmaster.com ### 2. Start +Build the images on the host (simplest — no registry needed): + ```bash -docker compose up -d +docker compose -f docker-compose.dev.yml up -d ``` +The default `docker-compose.yml` instead **pulls pre-built images** from a registry (faster restarts; +set `REGISTRY`/`TAG` in `.env`, `npm run push-images` from a dev machine, then `docker compose up -d`). + Frontend on port 8080, backend on port 3700, scrobble service on port 3710. Route each through your reverse proxy. **Important:** the scrobble subdomain must *not* sit behind an SSO / forward-auth gateway — it authenticates itself via Navidrome (`ping.view`), and a gateway in front redirects the app's API calls to a login page. ### 3. First launch diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..09e4a04 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,75 @@ +services: + + # ── Frontend (nginx) ────────────────────────────────────────────────────────── + novasonic-frontend: + build: . + container_name: novasonic-frontend + restart: unless-stopped + ports: + - "8080:80" # route this port through your reverse proxy + environment: + - NAVIDROME_URL=${NAVIDROME_URL} # pre-filled on the setup screen + - BACKEND_URL=${BACKEND_URL} # pre-filled on the setup screen + - SCROBBLE_URL=${SCROBBLE_URL} # play-log / stats service (setup screen) + + # ── Backend (yt-dlp download server) ───────────────────────────────────────── + novasonic-backend: + image: node:22-slim + container_name: novasonic-backend + restart: unless-stopped + working_dir: /app + volumes: + - ./backend:/app:ro # mount entire backend dir (server + lib/) + - ${MUSIC_PATH}:${MUSIC_PATH} # exact path from .env, no remapping + - novasonic-data:/data # writable data dir for overrides, state, etc. + ports: + - "3700:3700" # route this port through your reverse proxy + environment: + - PORT=3700 + - MUSIC_PATH=${MUSIC_PATH} # same path inside the container + - DATA_PATH=/data + - AUDIO_FMT=${AUDIO_FMT:-mp3} + - AUDIO_QUALITY=${AUDIO_QUALITY:-0} + - MAX_PARALLEL=${MAX_PARALLEL:-2} + - DOWNLOAD_BACKEND=${DOWNLOAD_BACKEND:-ytdlp} + - TICKETMASTER_API_KEY=${TICKETMASTER_API_KEY} + # Android APK download button — backend proxies a fixed Gitea release ("apk-latest"), same LAN. + # Optional: leave unset to use the local /data/novasonic.apk fallback instead. + - GITEA_URL=${GITEA_URL:-} # internal Gitea base, e.g. http://gitea:3000 + - GITEA_REPO=${GITEA_REPO:-} # owner/repo, e.g. Docker/NovaSonic + - GITEA_TOKEN=${GITEA_TOKEN:-} # read token (only for a private repo) + - GITEA_APK_TAG=${GITEA_APK_TAG:-apk-latest} + command: > + sh -c " + apt-get update && apt-get install -y --no-install-recommends python3 python3-pip ffmpeg curl git unzip && rm -rf /var/lib/apt/lists/* && + pip install --break-system-packages -U --pre 'yt-dlp[default]' && + curl -fsSL https://deno.land/install.sh | sh && + pip install --break-system-packages spotapi 'git+https://github.com/TzurSoffer/spotify-downloader' && + mkdir -p /deps && cd /deps && npm install express kuromoji && + cd /app && PATH=/root/.deno/bin:$$PATH NODE_PATH=/deps/node_modules node novasonic-server.js + " + + # ── Scrobble service (isolated timestamped play-log → stats + Wrapped) ──────── + novasonic-scrobble: + image: node:22-slim + container_name: novasonic-scrobble + restart: unless-stopped + working_dir: /app + volumes: + - ./scrobble:/app:ro # server code (read-only) + - novasonic-scrobble-data:/data # own DB volume, isolated from downloads + ports: + - "3710:3710" # route this port through your reverse proxy + environment: + - PORT=3710 + - DATA_PATH=/data + - NAVIDROME_URL=${NAVIDROME_URL} # for ping.view auth verification + command: > + sh -c " + mkdir -p /deps && cd /deps && npm install express && + cd /app && NODE_PATH=/deps/node_modules node --experimental-sqlite scrobble-server.js + " + +volumes: + novasonic-data: # persistent storage for romaji overrides and app state + novasonic-scrobble-data: # persistent play-log DB (novasonic-plays.db) diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml deleted file mode 100644 index fa44c35..0000000 --- a/docker-compose.prod.yml +++ /dev/null @@ -1,63 +0,0 @@ -# Production stack — PULLS pre-built images instead of building on the host. -# -# docker compose -f docker-compose.prod.yml pull -# docker compose -f docker-compose.prod.yml up -d -# -# Build & push the images from your dev machine with: npm run push-images -# (see scripts/build-push-images.mjs). Set REGISTRY + TAG in .env (see .env.example). -services: - - novasonic-frontend: - image: ${REGISTRY}/novasonic-frontend:${TAG:-latest} - pull_policy: always - container_name: novasonic-frontend - restart: unless-stopped - ports: - - "8080:80" - environment: - - NAVIDROME_URL=${NAVIDROME_URL} - - BACKEND_URL=${BACKEND_URL} - - SCROBBLE_URL=${SCROBBLE_URL} - - novasonic-backend: - image: ${REGISTRY}/novasonic-backend:${TAG:-latest} - pull_policy: always - container_name: novasonic-backend - restart: unless-stopped - volumes: - - ${MUSIC_PATH}:${MUSIC_PATH} # exact path from .env, no remapping - - novasonic-data:/data - ports: - - "3700:3700" - environment: - - PORT=3700 - - MUSIC_PATH=${MUSIC_PATH} - - DATA_PATH=/data - - AUDIO_FMT=${AUDIO_FMT:-mp3} - - AUDIO_QUALITY=${AUDIO_QUALITY:-0} - - MAX_PARALLEL=${MAX_PARALLEL:-2} - - DOWNLOAD_BACKEND=${DOWNLOAD_BACKEND:-ytdlp} - - TICKETMASTER_API_KEY=${TICKETMASTER_API_KEY} - # Android APK download button — backend proxies the fixed Gitea release ("apk-latest"). - - GITEA_URL=${GITEA_URL:-} - - GITEA_REPO=${GITEA_REPO:-} - - GITEA_TOKEN=${GITEA_TOKEN:-} - - GITEA_APK_TAG=${GITEA_APK_TAG:-apk-latest} - - novasonic-scrobble: - image: ${REGISTRY}/novasonic-scrobble:${TAG:-latest} - pull_policy: always - container_name: novasonic-scrobble - restart: unless-stopped - volumes: - - novasonic-scrobble-data:/data - ports: - - "3710:3710" - environment: - - PORT=3710 - - DATA_PATH=/data - - NAVIDROME_URL=${NAVIDROME_URL} # for ping.view auth verification - -volumes: - novasonic-data: - novasonic-scrobble-data: diff --git a/docker-compose.yml b/docker-compose.yml index 09e4a04..546af12 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,75 +1,64 @@ +# Default (production) stack — PULLS pre-built images instead of building on the host. +# +# docker compose pull +# docker compose up -d +# +# Build & push the images from your dev machine with: npm run push-images +# (see scripts/build-push-images.mjs). Set REGISTRY + TAG in .env (see .env.example). +# To build on the host instead (dev), use docker-compose.dev.yml. services: - # ── Frontend (nginx) ────────────────────────────────────────────────────────── novasonic-frontend: - build: . + image: ${REGISTRY}/novasonic-frontend:${TAG:-latest} + pull_policy: always container_name: novasonic-frontend restart: unless-stopped ports: - - "8080:80" # route this port through your reverse proxy + - "8080:80" environment: - - NAVIDROME_URL=${NAVIDROME_URL} # pre-filled on the setup screen - - BACKEND_URL=${BACKEND_URL} # pre-filled on the setup screen - - SCROBBLE_URL=${SCROBBLE_URL} # play-log / stats service (setup screen) + - NAVIDROME_URL=${NAVIDROME_URL} + - BACKEND_URL=${BACKEND_URL} + - SCROBBLE_URL=${SCROBBLE_URL} - # ── Backend (yt-dlp download server) ───────────────────────────────────────── novasonic-backend: - image: node:22-slim + image: ${REGISTRY}/novasonic-backend:${TAG:-latest} + pull_policy: always container_name: novasonic-backend restart: unless-stopped - working_dir: /app volumes: - - ./backend:/app:ro # mount entire backend dir (server + lib/) - ${MUSIC_PATH}:${MUSIC_PATH} # exact path from .env, no remapping - - novasonic-data:/data # writable data dir for overrides, state, etc. + - novasonic-data:/data ports: - - "3700:3700" # route this port through your reverse proxy + - "3700:3700" environment: - PORT=3700 - - MUSIC_PATH=${MUSIC_PATH} # same path inside the container + - MUSIC_PATH=${MUSIC_PATH} - DATA_PATH=/data - AUDIO_FMT=${AUDIO_FMT:-mp3} - AUDIO_QUALITY=${AUDIO_QUALITY:-0} - MAX_PARALLEL=${MAX_PARALLEL:-2} - DOWNLOAD_BACKEND=${DOWNLOAD_BACKEND:-ytdlp} - TICKETMASTER_API_KEY=${TICKETMASTER_API_KEY} - # Android APK download button — backend proxies a fixed Gitea release ("apk-latest"), same LAN. - # Optional: leave unset to use the local /data/novasonic.apk fallback instead. - - GITEA_URL=${GITEA_URL:-} # internal Gitea base, e.g. http://gitea:3000 - - GITEA_REPO=${GITEA_REPO:-} # owner/repo, e.g. Docker/NovaSonic - - GITEA_TOKEN=${GITEA_TOKEN:-} # read token (only for a private repo) + # Android APK download button — backend proxies the fixed Gitea release ("apk-latest"). + - GITEA_URL=${GITEA_URL:-} + - GITEA_REPO=${GITEA_REPO:-} + - GITEA_TOKEN=${GITEA_TOKEN:-} - GITEA_APK_TAG=${GITEA_APK_TAG:-apk-latest} - command: > - sh -c " - apt-get update && apt-get install -y --no-install-recommends python3 python3-pip ffmpeg curl git unzip && rm -rf /var/lib/apt/lists/* && - pip install --break-system-packages -U --pre 'yt-dlp[default]' && - curl -fsSL https://deno.land/install.sh | sh && - pip install --break-system-packages spotapi 'git+https://github.com/TzurSoffer/spotify-downloader' && - mkdir -p /deps && cd /deps && npm install express kuromoji && - cd /app && PATH=/root/.deno/bin:$$PATH NODE_PATH=/deps/node_modules node novasonic-server.js - " - # ── Scrobble service (isolated timestamped play-log → stats + Wrapped) ──────── novasonic-scrobble: - image: node:22-slim + image: ${REGISTRY}/novasonic-scrobble:${TAG:-latest} + pull_policy: always container_name: novasonic-scrobble restart: unless-stopped - working_dir: /app volumes: - - ./scrobble:/app:ro # server code (read-only) - - novasonic-scrobble-data:/data # own DB volume, isolated from downloads + - novasonic-scrobble-data:/data ports: - - "3710:3710" # route this port through your reverse proxy + - "3710:3710" environment: - PORT=3710 - DATA_PATH=/data - NAVIDROME_URL=${NAVIDROME_URL} # for ping.view auth verification - command: > - sh -c " - mkdir -p /deps && cd /deps && npm install express && - cd /app && NODE_PATH=/deps/node_modules node --experimental-sqlite scrobble-server.js - " volumes: - novasonic-data: # persistent storage for romaji overrides and app state - novasonic-scrobble-data: # persistent play-log DB (novasonic-plays.db) + novasonic-data: + novasonic-scrobble-data: diff --git a/scripts/build-push-images.mjs b/scripts/build-push-images.mjs index 6a0338b..5192212 100644 --- a/scripts/build-push-images.mjs +++ b/scripts/build-push-images.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node // Build the three NovaSonic images and push them to your registry, so the production host just PULLS -// (docker-compose.prod.yml) instead of building. Run from your dev machine after changes: +// (docker-compose.yml) instead of building. Run from your dev machine after changes: // // npm run push-images # builds + pushes REGISTRY/*:TAG (and :latest) // @@ -53,4 +53,4 @@ for (const [name, ctx] of Object.entries(images)) { } console.log(`\n✔ Alle Images gebaut + gepusht (${REGISTRY}/*:${TAG}). Auf dem Server:`); -console.log(' docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d'); +console.log(' docker compose pull && docker compose up -d');