Studio is a self-hosted private music workspace for managing work-in-progress albums, track versions, creative notes, and share links.
For Ubuntu server + Docker deployment (including mergeFS data path setup), see:
- Overview
- Website
- Features
- Tech Stack
- Project Structure
- Getting Started
- Environment Variables
- NPM Scripts
- How Data Is Stored
- Share Access Modes
- Frontend Build Notes
- Troubleshooting
- License
Studio is designed for producers, artists, and collaborators who want a private, self-hosted place to:
- Upload and organize projects
- Keep multiple versions of each track
- Track status, key, BPM, loudness, notes, and todos
- Manage cover versions
- Build moodboards and inspiration references
- Share project views with different access levels
It uses a lightweight backend with JSON + filesystem storage so it is easy to run without external services.
- Private account system with bcrypt password hashing
- First-run admin bootstrap flow
- Session-based authentication with cookies
- Home library with sortable project cards
- Project page with editable title/artist/status
- Track uploads (
.wav,.mp3,.flac) - Track versioning and active-version switching
- Cover versioning and active-cover switching
- WaveSurfer.js mini player with queue, shuffle, loop, and volume controls
- Per-track metadata:
- BPM, key, Camelot tag
- status and mood tags
- LUFS and peak values
- notes, lyrics, todos
- Project metadata dialogs:
- start/release dates
- completion percent
- star rating
- color palette and moodboard
- streaming checklist and distributor notes
- Share links with access levels:
view(See Only)listen(See + Listen)edit(See + Edit + Listen)
- Autosave-driven editing UX across major dialogs/sheets
- Frontend:
- React 18
- TypeScript
- Vite
- Tailwind CSS
- Radix UI primitives
- TanStack Query
- WaveSurfer.js
- Backend:
- Node.js + Express
- express-session
- multer (uploads)
- bcrypt
- music-metadata
- Storage:
- JSON database file
- Local filesystem for audio/covers
.
|- src/ # React app source
| |- components/ # UI + feature components
| |- hooks/ # React hooks (player, autosave, data hooks)
| |- pages/ # Route-level pages
| |- lib/ # Utilities, constants, mappers
| `- types/ # Shared TS types
|- static/ # Static assets copied by Vite
|- public/ # Built frontend output (served by Express)
|- data/ # Runtime data (db + uploaded files)
|- server.js # Express API + file serving
|- docker-compose.yml # Docker setup
|- Dockerfile # Container image build
|- SERVER_SETUP.md # Ubuntu server deployment guide
`- index.html # Vite source HTML template
- Node.js 20+
- npm 10+
- Optional: Docker + Docker Compose
npm install
npm run devThis starts:
- API server in
--api-onlymode onhttp://localhost:3000 - Vite dev server on
http://localhost:5173
npm install
npm run build:web
npm startThis runs Express on PORT and serves built frontend files from public/.
docker compose up -d --buildThen open:
http://localhost:<PORT>
If you changed frontend source and want those changes included in the image, build web assets before creating the image:
npm run build:web
docker compose up -d --buildUse .env (or your shell environment).
| Variable | Default | Used By | Notes |
|---|---|---|---|
PORT |
3000 |
API + app server | HTTP port Express listens on |
SESSION_SECRET |
studio-session-secret |
API | Change in production |
BCRYPT_COST |
12 |
API | Password hash cost (8-14 allowed by server) |
DATA_DIR |
./data |
API | Absolute/relative path for db + files |
DATA_PATH |
./data |
Docker Compose | Host path mounted into container as /data |
APP_PASSWORD |
studio |
Docker Compose env only | Currently not used by app logic |
Example .env:
PORT=3000
SESSION_SECRET=change-this
BCRYPT_COST=12| Script | What it does |
|---|---|
npm start |
Runs Express server (server.js) |
npm run dev |
Runs API + Vite dev servers in parallel |
npm run dev:api |
Runs API only (node server.js --api-only) |
npm run dev:web |
Runs Vite dev server |
npm run build:web |
Builds frontend into public/ |
npm run preview:web |
Serves built frontend preview with Vite |
npm run typecheck |
Runs TypeScript checks (tsc --noEmit) |
By default, runtime data is under ./data:
data/db.json- JSON database (users, projects, tracks, metadata)data/files/audio/- uploaded audio and track versionsdata/files/covers/- uploaded cover versions
In Docker, the container uses /data and Compose maps that to DATA_PATH on the host.
Studio supports link-based share sessions:
view- See onlylisten- See + listenedit- See + edit + listen
These permissions are enforced by API routes under /api/share/:token/....
There are intentionally two index.html files in this setup:
- Root
index.htmlis the Vite source template used during development/build. public/index.htmlis generated build output served by Express in non-API-only mode.
legacy-styles.css is not part of runtime imports; it is a legacy reference stylesheet used during style migration/matching work.
- App loads but styles/scripts look stale:
- Run
npm run build:webagain and restart the server/container.
- Run
- Upload errors or missing files:
- Verify
DATA_DIR/DATA_PATHexists and is writable.
- Verify
- Session/login issues:
- Confirm
SESSION_SECRETis set consistently across restarts.
- Confirm
- Vite works but direct Express run looks different:
- Vite serves live source; Express serves built
public/output.
- Vite serves live source; Express serves built
- Docker build runs but app looks outdated:
- Rebuild frontend artifacts before
docker compose up --build.
- Rebuild frontend artifacts before
MIT. See LICENSE.