Development Guide
Using Development Builds
Want to try features before they hit a stable release? Youtarr publishes a bleeding-edge dev-latest image that tracks the dev branch. Every merge to dev rebuilds it, so it's the fastest way to test unreleased work. It hasn't been through the stabilization pass that latest gets, so expect rough edges and occasional breakage. It's best used for testing and feedback, not as a daily driver.
The dev-latest tag always points at the most recent dev build. Each commit also gets an immutable dev-rc.<sha> tag if you want to pin to a specific build.
Recommended: ./start.sh --dev
./start.sh --dev --pull-latest
This pulls dialmaster/youtarr:dev-latest and starts the stack. On later runs, drop --pull-latest if you want to stay on the image you already have locally.
Alternative: bypass ./start.sh
./start.sh layers docker-compose.arm.yml on top of the default compose file for fresh installs so MariaDB uses a named volume. Existing installs with real MariaDB data in ./database/ keep using the bind mount and print a migration warning instead. ARM installs continue to use the named-volume override.
To pull the dev image while managing compose file selection yourself, use docker directly:
./stop.sh
docker pull dialmaster/youtarr:dev-latest
YOUTARR_IMAGE=dialmaster/youtarr:dev-latest docker compose up -d
This uses only docker-compose.yml and leaves your MariaDB volume config alone.
Switching back to the stable release
Restart without the YOUTARR_IMAGE override:
./start.sh
# or, if you run compose directly
docker compose up -d
If you set YOUTARR_IMAGE in .env, remove or comment that line first, otherwise compose will keep using the dev image.
Prerequisites
For development, you'll need:
- Node.js 20.19+ and npm 11.10+ (needed for the build script that compiles the client and installs dependencies before Docker runs)
- Docker and Docker Compose (v2 or v1)
- Git for version control
- A code editor (VS Code recommended)
Note: Runtime dependencies (MariaDB, yt-dlp, ffmpeg, Node) run inside the dev containers, but the build-dev script uses your host Node.js to install packages and build the frontend before the Docker image is created.
npm version requirement and install cooldown
Installs are gated on a 5-day cooldown: npm refuses to install any package version published less than 5 days ago (min-release-age=5 in .npmrc). This is a supply-chain defense; most malicious package releases are caught and removed within a day or two, so waiting before adopting a new version avoids the bulk of the risk. The cooldown applies at resolution time (npm install / npm update), not to npm ci, so your day-to-day builds against the committed lockfile are unaffected.
The cooldown requires npm 11.10 or newer, which is enforced via engines in package.json plus engine-strict=true in .npmrc. If your local npm is older, npm ci / npm install will fail with an engine error instead of silently skipping the cooldown. Upgrade with:
# Pin to an npm 11.10+ release that is itself older than the 5-day cooldown
# window (this global install is not lockfile-pinned, so it is not cooldown-protected),
# matching the pin used by CI and the Docker build.
npm install -g npm@11.15.0 --ignore-scripts
npm --version # confirm >= 11.10.0
CI and the Docker build pin npm to a known 11.x via a "Pin npm" step / npm install -g, so they honor the same gate.
If you ever need an urgent dependency update that the cooldown blocks (for example a same-day security patch), override it for that one install with npm install <pkg> --before=<date> or a temporary --min-release-age=0, rather than removing the repo-wide setting.
Lifecycle scripts are disabled by default. .npmrc sets ignore-scripts=true, so a dependency's preinstall / install / postinstall scripts do not run just because you install it. This blocks the most common malicious-package execution path. The CI and Docker installs already passed --ignore-scripts; this extends the same protection to a bare local npm install. Explicitly invoked scripts still run, so Husky setup via npm run prepare (used by build-dev.sh) is unaffected. If you add a dependency that needs a native build step, run npm rebuild <pkg> --ignore-scripts=false after confirming you trust the package; a plain npm rebuild inherits ignore-scripts=true and would still skip the build.
Project Structure
Youtarr/
├── client/ # React frontend
│ ├── src/
│ │ ├── components/ # React components
│ │ ├── types/ # TypeScript definitions
│ │ └── providers/ # Context providers
│ └── package.json
├── server/ # Node.js backend
│ ├── models/ # Sequelize models
│ ├── modules/ # Business logic
│ ├── db.js # Database connection
│ └── server.js # Express server
├── migrations/ # Database migrations
├── config/ # Configuration files
├── scripts/ # Utility scripts
├── docker-compose.external-db.yml # Config that does not start internal DB (dev & prod)
└── docker-compose.yml # Docker Compose configuration with internal db (dev & prod)
Development Setup
1. Clone Repository
git clone https://github.com/DialmasterOrg/Youtarr.git
cd Youtarr
2. Build Development Environment
# First build (installs dependencies and builds the client)
./scripts/build-dev.sh --install-deps
# Subsequent builds (after code changes)
./scripts/build-dev.sh
Optional flags:
--install-deps- Runsnpm ci --ignore-scriptsin the root andclient/before building, then runsnpm run prepareonce to wire up Husky git hooks (Husky's own script only; dependency lifecycle scripts stay disabled). Required on a clean clone or after dependency changes.--no-cache- Force rebuild to get latest yt-dlp versionSKIP_DEV_IMAGE_PRUNE=1- Skip automatic cleanup of old untaggedyoutarr-devimages (pruning is enabled by default to keep Docker storage from filling)
The script runs npm run build for the client and then invokes docker build, so make sure Node.js 20.19+ and npm are available locally.
3. Configure .env (optional)
- Copy
.env.exampleto.env- This is optional, if not created manually it will be created by the
./scripts/start-dev.shscript - Edit
.envto configure your YOUTUBE_OUTPUT_DIR- This will be the directory that is mounted by Youtarr where downloaded videos will be placed
- It defaults to
./downloads
- Leave
AUTH_PRESET_USERNAMEandAUTH_PRESET_PASSWORDblank to configure your login via UI on first startup (credentials will be saved toconfig/config.json)
- This is optional, if not created manually it will be created by the
Development container ownership
The development app honors YOUTARR_UID and YOUTARR_GID, just like production.
Set both to your host user's numeric IDs (id -u and id -g) in .env when you
want generated files to be owned by your development account. For example:
YOUTARR_UID=1000
YOUTARR_GID=1000
Unset values retain the root (0:0) default. These settings apply to the app,
not MariaDB. Older versions of docker-compose.dev.yml ignored these variables,
so switching to development could run the app as root even with IDs configured.
Changing .env or this Compose setting does not repair existing file ownership,
and docker start alone does not apply a new container user.
For an existing local development installation with mixed ownership:
-
Stop the app before changing file ownership:
docker stop youtarr-dev -
Set the intended IDs in
.env. For a dedicated local checkout whose app should run as1000:1000, repair only its configuration and metadata paths:# Run from the repository root; replace the IDs if yours differ.
mkdir -p config jobs server/images
sudo chown -R 1000:1000 config jobs server/imagesThis deliberately assigns these app files to the selected development user; it preserves their existing permission modes. Check that the owner has write access to the needed files and directories. Do not recursively chown the repository,
database/, database volumes, or a shared media library. Custom bind mounts require using their actual host paths instead. If changing ownership fails, check your storage permissions before continuing. -
Ensure the configured
YOUTUBE_OUTPUT_DIRis also writable by the selected app account. Repair ownership only for files belonging to this installation, or arrange appropriate permissions for shared storage. Mounted source code must remain readable by the app user too. -
Recreate the development containers through the normal startup command, then verify the effective app user:
./scripts/start-dev.sh
docker inspect youtarr-dev --format '{{.Config.User}}'
docker exec youtarr-dev idThe example configuration should report
1000:1000and UID/GID 1000. Download a small video and check ownership of newly generated metadata/images. If IDs differ, check shell overrides and custom Compose files. Merely restarting the old container retains its old user setting.
These steps repair files created by older development containers. Before backup
or restore, stop the app again with docker stop youtarr-dev.
4. Start Development Environment
Step 1: Full Docker Development
Build and run the full stack using the pre-built static frontend served by the app container.
# Start both app and database containers (serves static frontend)
./scripts/start-dev.sh
This starts:
- Backend on http://localhost:3011 (Node.js Express server with
--watchfor auto-restart) - Frontend (static, served by the app container) on http://localhost:3087
- MariaDB database on the internal Docker network only
Optional flags:
--no-auth- Disable authentication (only use behind auth gateway or if not exposed outside your network)--debug- Set logging level to "debug" (defaults toinfo)
Step 2: Vite Dev Server (Hot Module Reload — optional)
For faster, iterative frontend development you can run the Vite dev server with HMR. This is optional; run it when you want instant frontend reloads while developing UI.
# Terminal 1: Start backend in Docker
./scripts/start-dev.sh
# Terminal 2: Start Vite dev server (HMR)
cd client
npm run dev
Then access:
- Frontend (HMR) at http://localhost:3000
The Vite dev server will proxy API and WebSocket requests to the backend at port 3011 so API calls work the same as the full-stack run.
The dev server binds to all interfaces (0.0.0.0) by default so workflows that reach the host from another network namespace (Docker, WSL2, remote dev containers) work without extra configuration. Override with the VITE_HOST env var (e.g. VITE_HOST=localhost npm run dev) if you want to bind to loopback only.
5. Storybook (Component Development)
Use Storybook to develop and document components in isolation.
Prerequisites: Complete Step 2 (Build Development Environment) first, or at minimum install client dependencies:
cd client && npm ci --ignore-scripts
The MSW (Mock Service Worker) file used for Storybook API mocking is committed at client/public/mockServiceWorker.js. After bumping msw, regenerate it with cd client && npm run msw:init and commit the updated worker.
# Start Storybook Server
npm run storybook
Storybook will open automatically in your browser. Story validation is done via Jest tests (see client/src/tests/storybook_coverage.test.js).
6. Access the Application
Navigate to:
- Docker static build: http://localhost:3087
- Vite dev server (if running): http://localhost:3000
Create your admin account on first access.
7. Stop Development Environment
./stop.sh
Resetting local development environment
!IMPORTANT!: This COMPLETELY resets your local environment and REMOVES all data except your downloaded videos!
DATABASE AND CONFIGURATION IS REMOVED AND NOT BACKED UP!
This can be useful for local development and testing
./scripts/reset-server-data.sh
How Docker Development Works
Build-and-Test Workflow
The development setup is a "build-and-test-in-Docker" workflow that ensures your code works in a containerized environment:
Build Phase (./scripts/build-dev.sh):
- Runs
npm ci --ignore-scriptson your host (if--install-depsis used) - Builds the React frontend (
npm run buildin client directory) - creates production build - Builds a Docker image with the pre-built static files
Runtime Phase (./scripts/start-dev.sh):
- Runs the Node.js Express server with
node --watch server/server.js- backend code changes auto-restart the server without a rebuild - Serves the pre-built React static files from
/app/client/build(frontend changes require a rebuild unless you are running the Vite dev server) - Application accessible at http://localhost:3087
Volume Mounts
The development setup (docker-compose.dev.yml) mounts these directories into the container:
volumes:
- ${YOUTUBE_OUTPUT_DIR:-./downloads}:/usr/src/app/data # Downloaded videos
- ./server/images:/app/server/images # Generated thumbnails
- ./config:/app/config # Configuration files
- ./jobs:/app/jobs # Job state
# Backend source and migrations for hot reload with --watch
- ./server:/app/server
- ./migrations:/app/migrations
- ./package.json:/app/package.json
- ./package-lock.json:/app/package-lock.json
Backend hot reload: ./server/ is mounted and the container runs node --watch server/server.js, so backend code changes auto-restart the server without a rebuild. Check the container logs to confirm the restart.
Frontend: client/src/ is NOT mounted. Frontend changes reach the running app one of two ways:
- Full rebuild:
./scripts/build-dev.shrebuilds the static bundle that the app container serves at http://localhost:3087. - Vite dev server (recommended): run
cd client && npm run devin a second terminal. Vite serves the frontend on http://localhost:3000 with HMR and proxies API/WebSocket calls to the backend on host port 3087 (whichdocker-compose.dev.ymlforwards to container port 3011). Override withVITE_BACKEND_PORTif you have remapped the host port.
When to Rebuild
You must rebuild (./scripts/build-dev.sh) for:
- Frontend code changes when you are using the static-bundle workflow (not needed if you are using the Vite dev server).
- Installing new npm dependencies (use
--install-depsflag). - Updating system dependencies (yt-dlp, ffmpeg - use
--no-cacheflag). - Changing the Dockerfile.
- First time setup.
You do not need to rebuild for:
- Backend code changes (server/*.js, modules, routes) -
node --watchpicks them up automatically. - New migration files -
./migrations/is mounted, though you still need to restart the container for them to run. - Frontend changes while the Vite dev server is running - Vite HMR updates the browser.
Benefits of This Approach
- ✅ Tests your code in the actual Docker environment it will run in
- ✅ Catches Docker-specific issues early
- ✅ Consistent behavior between development and production
- ✅ Database runs in Docker (no local MariaDB installation needed)
- ✅ Includes all system dependencies (yt-dlp, ffmpeg)
Development Workflow
Daily Development
Vite Dev Server Workflow (Recommended for Frontend)
# Terminal 1: Start backend
./scripts/start-dev.sh
# Terminal 2: Start Vite with HMR
cd client
npm run dev
# Make code changes - frontend updates instantly!
# Backend changes auto-restart via --watch
# View logs
docker compose -f docker-compose.dev.yml logs -f youtarr
# Stop when done
./stop.sh
Full Docker Workflow (For Testing Production Build)
# Start development environment
./scripts/start-dev.sh
# Make code changes in your editor
# After making changes, rebuild and restart:
./scripts/build-dev.sh
./scripts/start-dev.sh # Automatically stops and restarts containers
# View logs
docker compose -f docker-compose.dev.yml logs -f
# Stop when done
./stop.sh
Note: Youtarr only supports the Docker-based workflow described here. Always build and test inside the dev containers rather than trying to run the backend or frontend directly on the host.
Working with Containers
# View running containers
docker compose ps
# Execute commands in app container
docker compose exec youtarr bash
# Execute commands in database container
docker compose exec youtarr-db bash
# Restart a specific service
docker compose restart youtarr
# View logs for specific service
docker compose logs -f youtarr
docker compose logs -f youtarr-db
Database Access
# From inside the database container
docker compose exec youtarr-db mysql -u root -p123qweasd youtarr
Code Style and Linting
ESLint Configuration
The project uses ESLint for code quality. Configuration is in .eslintrc.js.
# Lint all code
npm run lint
# Lint with auto-fix
npm run lint:fix
# Lint specific areas
npm run lint:frontend
npm run lint:backend
# TypeScript type checking
npm run lint:ts
Pre-commit Hooks
Husky is configured to run linting, typescript checks and tests before commits. To bypass (not recommended):
git commit --no-verify
Testing
Running Tests
All tests run on your host machine (not in Docker) since they're isolated unit tests:
# Run all tests (backend + frontend)
npm test
# Backend tests only
npm run test:backend
# Frontend tests only
npm run test:frontend
# Run with coverage
npm run test:coverage
# Watch mode (backend)
npm run test:watch
External Cookie Validation Tests
The backend Jest suites cover external-file handling and process cleanup. A separate Python suite checks the helper against yt-dlp's actual cookie loader, including malformed records and suppression of cookie values in diagnostics. It runs locally without contacting YouTube or downloading videos:
python3 -m unittest discover -s server/utils/__tests__ -p 'test_validate_cookies.py'
This requires Python and yt-dlp's platform-independent zipimport executable on
PATH, the distribution used by the Docker image. Alternatively, set
YOUTARR_TEST_YTDLP to that executable's absolute path. CI downloads the current
release and runs this suite in the yt-dlp Cookie Loader Tests job, required
by All Checks. The helper imports that executable so the parser follows
yt-dlp updates; it does not maintain a separate installation or format parser.
Frontend Tests
cd client
npm test # Watch mode
npm test -- --coverage # With coverage
npm test -- --watchAll=false # Run once
Database Development
Working with Migrations
# Create a new migration
./scripts/db-create-migration.sh migration-name
# Migrations run automatically on container startup
Database Schema
See DATABASE.md
Backend Debugging
Option 1: Container Debugging with VS Code
Create .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "Docker: Attach to Node",
"remoteRoot": "/usr/src/app",
"localRoot": "${workspaceFolder}",
"protocol": "inspector",
"port": 9229,
"restart": true,
"sourceMaps": true
}
]
}
Modify docker-compose.yml to expose debug port:
ports:
- "3011:3011"
- "9229:9229" # Debug port
command: node --inspect=0.0.0.0:9229 server/server.js
Option 2: Logger Debugging
Use the project's Pino logger (not console.log) so output stays structured and log levels work:
const logger = require('../logger'); // adjust relative path as needed
logger.debug({ variable }, 'Debug info');
debugger; // Breakpoint
Set LOG_LEVEL=debug in your .env to surface logger.debug(...) output.
Option 3: Exec into Container
docker compose exec youtarr bash
# Now you can run commands, inspect files, etc.
Frontend Debugging
- React Developer Tools browser extension
- Network tab for API calls
- Console for errors and logs
- Source maps are included in the production build for debugging
Database Debugging
Enable Sequelize SQL logging in db.js by routing it through the Pino logger:
const logger = require('./logger');
const sequelize = new Sequelize({
// ... other config
logging: (sql) => logger.debug({ sql }, 'sequelize query')
});
Then set LOG_LEVEL=debug in your .env to see the queries.
API Development
Documentation site checks
Run npm run docs:check from the repository root to generate the documentation and run the documentation generator tests. This requires the root and website/ dependencies to be installed. CI runs the same check before building the site.
If documentation links point to missing files or unpublished Markdown pages, the generator reports all of those failures together. Each error includes the source file and line, the link target, the path it tried to find, and a suggested correction when it can identify the intended file. GitHub Actions also receives file-and-line error annotations. These errors stop generation; the documentation site build performs additional checks, including heading anchors.
Relative links start from the document's own folder. For example, a guide in docs/ links to a sibling page using BACKUP_RESTORE.md, without another docs/ prefix. A leading / is treated as a filesystem-root path by the generator. For repository files that aren't published as site assets, such as Compose YAML files, use full GitHub URLs so the links work on the published site too.
API Documentation (Swagger)
Youtarr provides interactive API documentation via Swagger UI:
- Swagger UI: http://localhost:3087/swagger
- OpenAPI JSON: http://localhost:3087/swagger.json
The Swagger documentation includes:
- All available endpoints with request/response schemas
- Authentication requirements
- Try-it-out functionality for testing endpoints
- Request body examples and parameter descriptions
API Structure
Routes are organized into modular files under ./server/routes/:
server/routes/
├── auth.js # Authentication endpoints
├── channels.js # Channel management
├── config.js # Configuration endpoints
├── health.js # Health check endpoints
├── jobs.js # Download job management
├── plex.js # Plex integration
├── setup.js # Initial setup endpoints
├── videos.js # Video management
└── index.js # Route registration
Key Endpoints
All endpoints require authentication except setup and health checks:
- Setup:
GET /setup/status,POST /setup/create-auth - Auth:
POST /auth/login,POST /auth/logout,GET /auth/sessions - Config:
GET /getconfig,POST /updateconfig - Channels:
GET /getchannels,POST /updatechannels - Downloads:
POST /triggerspecificdownloads,GET /jobstatus/:jobId - Plex:
GET /getplexlibraries,GET /plex/auth-url - Health:
GET /api/health,GET /api/db-status
For the complete list of 40+ endpoints, see the Swagger documentation.
Authentication
All API endpoints (except setup, login, and health checks) require authentication:
headers: {
'x-access-token': 'session-token-here'
}
Adding New Endpoints
When adding new API endpoints:
- Add the route to the appropriate file in
./server/routes/ - Include JSDoc annotations for Swagger documentation:
/**
* @swagger
* /api/your-endpoint:
* get:
* summary: Brief description
* description: Detailed description of what the endpoint does.
* tags: [CategoryTag]
* parameters:
* - in: query
* name: paramName
* schema:
* type: string
* description: Parameter description
* responses:
* 200:
* description: Success response description
*/
router.get('/api/your-endpoint', verifyToken, async (req, res) => {
// Implementation
});
- The Swagger documentation will automatically update on the next server restart
WebSocket Events
WebSocket shares the HTTP port (3011 in container, 3087 on host) and emits:
downloadProgress- Download progress updatesdownloadComplete- Video download finishedjobsUpdated- Download job enqueued or startedchannelsUpdated- Channel list changedvideosUpdated- A video's database rows were persisted mid-batchrescanStatus- Filesystem rescan started or finishedscheduledTaskStatus- A scheduled task run started or finished (payload{ key }); the Scheduling page refetches/api/scheduleschannelTabsDetected- Tab detection finished for a channelprogress/complete/error(sourcesubscriptionImport) - Subscription import job updates
The client also synthesizes a local connectionRestored message after every reconnect (client/src/providers/WebSocketProvider.tsx); the server never sends it.
Contributing
Branching Strategy
Youtarr uses a dev → main branching model:
| Branch | Purpose | Docker Tag |
|---|---|---|
main | Stable, released code | latest, vX.X.X |
dev | Integration branch for upcoming release | dev-latest, dev-rc.<sha> |
feature/*, fix/* | Individual changes | None |
Git Workflow
-
Start from dev branch:
git checkout dev
git pull origin dev
git checkout -b feat/your-feature -
Make changes and commit:
git add .
git commit -m "feat: add new feature" -
Push and create pull request targeting
dev(notmain) -
After merge to
dev, an RC Docker image is automatically built -
When ready, maintainer creates PR from
dev→mainfor production release
Commit Message Convention
Follow conventional commits for automatic versioning:
feat:- New feature (minor version bump)fix:- Bug fix (patch version bump)docs:- Documentation onlystyle:- Code style changesrefactor:- Code refactoringtest:- Test changeschore:- Build process or auxiliary tool changesBREAKING CHANGE:- Breaking change (major version bump)
Code Review Checklist
Before submitting PR:
- PR targets
devbranch - Code passes linting (
npm run lint) - All tests pass (
npm test) - Database migrations included (if needed)
- Documentation updated
- No sensitive data in commits
- Follows existing code style
Building Images
Docker Build
# Build local image
./scripts/build-dev.sh
# Test locally
./scripts/start-dev.sh
Release Process
Releases are automated via GitHub Actions with a two-stage workflow:
Release Candidates (automatic on dev merge):
- Merge your PR to
devbranch - RC workflow automatically:
- Builds multi-arch Docker images (amd64 + arm64)
- Pushes
dev-latestanddev-rc.<sha>tags to Docker Hub
Production Releases (automatic on main merge):
- Maintainer creates PR from
dev→main - After merge, release workflow automatically:
- Bumps version based on commit messages
- Updates CHANGELOG.md
- Creates GitHub release
- Builds optimized Docker image (~600MB)
- Pushes
latestandvX.X.Xtags to Docker Hub
The production workflow uses scripts/release-notes.js to strip the generated
version heading before sharing notes with GitHub Releases and Discord. Each
CHANGELOG.md entry has one ## release heading, followed by the category
headings and a full comparison link. Dry runs prepare and display the same entry
without updating the file or publishing a release.
Tag creation passes the unprefixed new_version to the tagging action and pins
the tag to the version-bump commit. Historical vvX.X.X tags are retained: they
can still be selected as the commit-analysis baseline during the transition to
new single-prefix tags. Public comparison links use the corresponding vX.X.X
release tags, whose existence is checked before publishing. These old tag pairs
can point to different commits, so canonical release comparisons can omit a
version-bump commit present in the legacy range. The dry-run output shows both
the canonical previous tag and the actual commit-analysis baseline.
Release-note regression checks can be run separately from the application suites:
node --test --test-concurrency=1 scripts/tests/release-notes.test.js
Troubleshooting Development Issues
Containers Won't Start
# Check container status
docker compose ps
# View logs
docker compose logs
# Rebuild from scratch
docker compose down -v
./scripts/build-dev.sh --no-cache
./scripts/start-dev.sh
Port Already in Use
# Find process using port
lsof -i :3011 # Mac/Linux
netstat -ano | findstr :3011 # Windows
# Or stop all Docker containers
docker compose down
Database Connection Issues
-
Check database is running:
docker compose ps youtarr-db -
Check database logs:
docker compose logs youtarr-db -
Test connection:
docker compose exec youtarr-db mysql -u root -p123qweasd youtarr
Code Changes Not Reflected
Backend and frontend reload differently in the dev setup.
Backend code changes (server/*.js, server/modules/, server/routes/, migrations/):
./server/and./migrations/are volume-mounted into the container and the container runsnode --watch.- Saves auto-restart the server within a few seconds; check the container logs to confirm the restart fired.
- No rebuild required. (New migration files still require a container restart to actually run.)
Frontend code changes (client/src/):
client/src/is NOT mounted into the app container; the static bundle is baked into the image at build time.- If you are running the Vite dev server (
cd client && npm run dev), HMR updates the browser automatically on http://localhost:3000 - no rebuild. - Otherwise, rebuild the static bundle:
./scripts/build-dev.sh
./scripts/start-dev.sh # automatically stops and restarts containers
If a backend change is not being picked up, verify the container is actually running node --watch (docker compose -f docker-compose.dev.yml logs youtarr should show restart messages when you save) and that you edited a file under ./server/.
Module Not Found Errors
# Rebuild with fresh dependencies
./scripts/build-dev.sh --install-deps
Security Considerations
Development Security
- Never commit
.envfiles - Use environment variables for secrets
- Sanitize user inputs
- Validate all API inputs
- Use prepared statements for SQL (Sequelize handles this)
- Keep dependencies updated
Security Testing
# Check for vulnerabilities
npm audit
# Fix vulnerabilities
npm audit fix
# Check frontend
cd client && npm audit
Performance Profiling
Backend Profiling
Add to server.js for request timing (use the Pino logger, not console.log):
const logger = require('./logger');
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
logger.info({ method: req.method, path: req.path, durationMs: Date.now() - start }, 'request');
});
next();
});
Frontend Profiling
Use React DevTools Profiler to identify performance bottlenecks.
Platform-Specific Development
When working on changes that interact with platform-managed deployments, see the dedicated developer guides:
- Elfhosted - environment variables, behavior switches, and how to spoof an Elfhosted deployment locally for testing.
Backup and restore script validation
The Python regression suite uses temporary fixtures and does not contact Docker or a database. Run it from the repository root:
python3 -m unittest discover -s scripts/tests -p 'test_backup_restore.py'
CI runs this command in a dedicated Python job required by All Checks.
The local pre-commit hook does not run it. Coverage includes database client
versions, monitoring mounts, table-name handling, database permissions and roles,
and restore preflight failures.
Fixture tests do not establish compatibility with real database servers. Validate backup/restore round trips and failed-import recovery on disposable installations, including bundled MariaDB 10.3 and external MariaDB 11.4/MySQL 8, as appropriate to the change. Use separate databases and storage, never a live installation's database volume.