mirror of
https://github.com/maxdorninger/MediaManager.git
synced 2026-04-17 15:13:24 +02:00
Improve local dev experience
This commit is contained in:
39
Dockerfile
39
Dockerfile
@@ -1,28 +1,16 @@
|
||||
FROM node:24-alpine AS frontend-build
|
||||
WORKDIR /frontend
|
||||
ARG VERSION
|
||||
ARG BASE_PATH=""
|
||||
|
||||
COPY web/package*.json ./
|
||||
RUN npm ci && npm cache clean --force
|
||||
|
||||
COPY web/ ./
|
||||
RUN env PUBLIC_VERSION=${VERSION} PUBLIC_API_URL=${BASE_PATH} BASE_PATH=${BASE_PATH}/web npm run build
|
||||
|
||||
FROM ghcr.io/astral-sh/uv:python3.13-trixie-slim
|
||||
ARG VERSION
|
||||
ARG BASE_PATH=""
|
||||
LABEL author="github.com/maxdorninger"
|
||||
LABEL version=${VERSION}
|
||||
LABEL description="Docker image for MediaManager"
|
||||
RUN env PUBLIC_VERSION=${VERSION} PUBLIC_API_URL=${BASE_PATH} BASE_PATH=${BASE_PATH}/web npm run build
|
||||
|
||||
ENV PUBLIC_VERSION=${VERSION} \
|
||||
CONFIG_DIR="/app/config"\
|
||||
BASE_PATH=${BASE_PATH}\
|
||||
FRONTEND_FILES_DIR="/app/web/build"
|
||||
|
||||
|
||||
WORKDIR /app
|
||||
FROM ghcr.io/astral-sh/uv:python3.13-trixie-slim AS base
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y ca-certificates bash libtorrent21 gcc bc locales postgresql media-types mailcap curl gzip unzip tar 7zip bzip2 unar && \
|
||||
@@ -34,8 +22,24 @@ RUN locale-gen
|
||||
ENV LANG=en_US.UTF-8
|
||||
ENV LC_ALL=en_US.UTF-8
|
||||
|
||||
FROM base AS dependencies
|
||||
WORKDIR /app
|
||||
|
||||
COPY pyproject.toml uv.lock ./
|
||||
RUN uv sync --locked
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv sync --locked
|
||||
|
||||
FROM dependencies AS app
|
||||
ARG VERSION
|
||||
ARG BASE_PATH=""
|
||||
LABEL author="github.com/maxdorninger"
|
||||
LABEL version=${VERSION}
|
||||
LABEL description="Docker image for MediaManager"
|
||||
|
||||
ENV PUBLIC_VERSION=${VERSION} \
|
||||
CONFIG_DIR="/app/config"\
|
||||
BASE_PATH=${BASE_PATH}\
|
||||
FRONTEND_FILES_DIR="/app/web/build"
|
||||
|
||||
COPY --chmod=755 mediamanager-startup.sh .
|
||||
COPY config.example.toml .
|
||||
@@ -43,8 +47,9 @@ COPY media_manager ./media_manager
|
||||
COPY alembic ./alembic
|
||||
COPY alembic.ini .
|
||||
|
||||
COPY --from=frontend-build /frontend/build /app/web/build
|
||||
|
||||
HEALTHCHECK CMD curl -f http://localhost:8000${BASE_PATH}/api/v1/health || exit 1
|
||||
EXPOSE 8000
|
||||
CMD ["/app/mediamanager-startup.sh"]
|
||||
|
||||
FROM app AS production
|
||||
COPY --from=frontend-build /frontend/build /app/web/build
|
||||
49
Makefile
Normal file
49
Makefile
Normal file
@@ -0,0 +1,49 @@
|
||||
SHELL := /bin/bash
|
||||
|
||||
# Docker Compose command (override if needed)
|
||||
COMPOSE ?= docker compose
|
||||
DC := $(COMPOSE) -f docker-compose.dev.yaml
|
||||
|
||||
# Log args passthrough, e.g.:
|
||||
# make logs ARGS="--follow --tail=100"
|
||||
ARGS ?=
|
||||
|
||||
# Service names (override if your compose uses different names)
|
||||
APP_SVC ?= mediamanager
|
||||
FRONTEND_SVC ?= frontend
|
||||
|
||||
.PHONY: help up down logs ps restartapp frontend
|
||||
|
||||
help:
|
||||
@echo "Usage:"
|
||||
@echo " All commands run using the dev docker compose file ($(DEV_FILE))"
|
||||
@echo ""
|
||||
@echo " make up # Development environment up, runs with --build flag to rebuild if necessary"
|
||||
@echo " make down # Development environment down"
|
||||
@echo " make logs ARGS=\"...\" # (Optional) Set ARGS like \"--follow --tail=100\""
|
||||
@echo " make ps | restart # Check status or restart containers"
|
||||
@echo " make app # Shell into $(APP_SVC) container"
|
||||
@echo " make frontend # Shell into $(FRONTEND_SVC) container"
|
||||
|
||||
# Core lifecycle
|
||||
up:
|
||||
$(DC) up -d --build
|
||||
|
||||
down:
|
||||
$(DC) down
|
||||
|
||||
logs:
|
||||
$(DC) logs $(ARGS)
|
||||
|
||||
ps:
|
||||
$(DC) ps
|
||||
|
||||
restart:
|
||||
$(DC) restart
|
||||
|
||||
# Interactive shells (prefer bash, fallback to sh)
|
||||
app:
|
||||
@$(DC) exec -it $(APP_SVC) bash 2>/dev/null || $(DC) exec -it $(APP_SVC) sh
|
||||
|
||||
frontend:
|
||||
@$(DC) exec -it $(FRONTEND_SVC) bash 2>/dev/null || $(DC) exec -it $(FRONTEND_SVC) sh
|
||||
@@ -11,13 +11,28 @@ This section is for those who want to contribute to Media Manager or understand
|
||||
|
||||
## Special Dev Configuration
|
||||
|
||||
#### Env Variables
|
||||
### Environment Variables
|
||||
|
||||
- `BASE_PATH`: this sets the base path for the app (can be set for both backend and frontend)
|
||||
- `PUBLIC_VERSION`: this sets the version variable, it is displayed in the frontend (requires rebuilding of the
|
||||
frontend) and in the /api/v1/health endpoint (can be set for both backend and frontend)
|
||||
- `FRONTEND_FILES_DIR`: directory for frontend files, e.g. in Docker container it is `/app/web/build` (only backend)
|
||||
- `MEDIAMANAGER_MISC__DEVELOPMENT`: If set to `TRUE`, enables hot reloading of FastAPI (only when using the docker container)
|
||||
MediaManager uses various environment variables for configuration. In the Docker development setup (`docker-compose.dev.yaml`), most of these are automatically configured for you.
|
||||
|
||||
#### Backend Variables
|
||||
- `BASE_PATH`: Sets the base path for the app (e.g., for subdirectory deployments)
|
||||
- `PUBLIC_VERSION`: Version string displayed in `/api/v1/health` endpoint
|
||||
- `FRONTEND_FILES_DIR`: Directory for built frontend files (e.g., `/app/web/build` in Docker)
|
||||
- `MEDIAMANAGER_MISC__DEVELOPMENT`: When set to `TRUE`, enables FastAPI hot-reloading in Docker
|
||||
|
||||
#### Frontend Variables
|
||||
- `PUBLIC_API_URL`: API URL for backend communication (automatically configured via Vite proxy in Docker)
|
||||
- `PUBLIC_VERSION`: Version string displayed in the frontend UI
|
||||
- `BASE_PATH`: Base path for frontend routing (matches backend BASE_PATH)
|
||||
|
||||
#### Docker Development Variables
|
||||
- `DISABLE_FRONTEND_MOUNT`: When `TRUE`, disables mounting built frontend files (allows separate frontend container)
|
||||
- **Note:** This is automatically set in `docker-compose.dev.yaml` to enable the separate frontend development container
|
||||
|
||||
#### Configuration Files
|
||||
- Backend: `res/config/config.toml` (created from `config.dev.toml`)
|
||||
- Frontend: `web/.env` (created from `.env.example`)
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -44,29 +59,68 @@ features.
|
||||
- VirtualKit
|
||||
- Writerside (for writing documentation)
|
||||
|
||||
### Other recommendations
|
||||
### Recommended Development Workflow
|
||||
|
||||
I recommend developing using Docker, i.e. you can use the provided `docker-compose.dev.yaml` file. This dev
|
||||
docker-compose file has the `./media_manager` directory mounted at `/app/media_manager` in the container, meaning you
|
||||
can run the code using the container in exactly the environment it will be running in.
|
||||
The **recommended way** to develop MediaManager is using the fully Dockerized setup with `docker-compose.dev.yaml`.
|
||||
This ensures you're working in the same environment as production and makes it easy for new contributors to get started without installing Python, Node.js, or other dependencies locally.
|
||||
|
||||
Additionally, to develop the frontend I use a locally installed Node.js server. So basically a hybrid approach, where
|
||||
the backend runs in a container and the frontend runs on Windows. To make this work, you need to make sure the
|
||||
`cors_urls` and `frontend_url` are set correctly in the backend's config file.
|
||||
The development environment includes:
|
||||
- **Backend (FastAPI)** with automatic hot-reloading for Python code changes
|
||||
- **Frontend (SvelteKit/Vite)** with Hot Module Replacement (HMR) for instant updates
|
||||
- **Database (PostgreSQL)** pre-configured and ready to use
|
||||
|
||||
Unfortunately, a side effect of this setup is that you have to rebuild the Docker image every time when you change the
|
||||
python dependencies in any way or at least restart the container if you change the code. For a fast-paced development it
|
||||
may be more convenient to run the backend locally too, because then it supports hot reloading.
|
||||
**What requires what:**
|
||||
- **Python code changes (.py files)**: Automatically detected and hot-reloaded, no action needed ✨
|
||||
- **Frontend code changes (.svelte, .ts, .css)**: Instant HMR updates in browser, no action needed ✨
|
||||
- **Configuration changes (config.toml)**: Live updates via volume mount, no action needed ✨
|
||||
- **Backend dependencies (pyproject.toml)**: Require rebuilding: `docker compose -f docker-compose.dev.yaml build mediamanager`
|
||||
- **Frontend dependencies (package.json)**: Restart frontend container: `docker compose -f docker-compose.dev.yaml restart frontend`
|
||||
- **Database migrations**: Automatically run on backend container startup
|
||||
|
||||
### Setting up the basic development environment with Docker
|
||||
This approach eliminates the need for container restarts during normal development and provides the best developer experience with instant feedback for code changes.
|
||||
|
||||
- Copy the `config.dev.toml` file to `config.toml` in the `./res` directory and edit it to your needs.
|
||||
- Use the following command to start the development environment with Docker:
|
||||
#### How the Frontend Connects to the Backend
|
||||
|
||||
In the Docker development setup, the frontend and backend communicate through Vite's proxy configuration:
|
||||
|
||||
- **Frontend runs on**: `http://localhost:5173` (exposed from Docker)
|
||||
- **Backend runs on**: `http://mediamanager:8000` (Docker internal network)
|
||||
- **Vite proxy**: Automatically forwards all `/api/*` requests from frontend to backend
|
||||
|
||||
This means when your browser makes a request to `http://localhost:5173/api/v1/tv/shows`, Vite automatically proxies it to `http://mediamanager:8000/api/v1/tv/shows`. The `PUBLIC_API_URL` environment variable is set to use this proxy, so you don't need to configure anything manually.
|
||||
|
||||
### Setting up the full development environment with Docker (Recommended)
|
||||
|
||||
This is the easiest and recommended way to get started. Everything runs in Docker with hot-reloading enabled.
|
||||
|
||||
1. **Copy the example config & .env:**
|
||||
```bash
|
||||
docker compose -f docker-compose.dev.yaml up -d
|
||||
cp config.dev.toml res/config/config.toml
|
||||
cp web/.env.example web/.env
|
||||
```
|
||||
|
||||
### Setting up the backend development environment
|
||||
2. **Start all services:**
|
||||
```bash
|
||||
# Recommended: Use make commands for easy development
|
||||
make up
|
||||
|
||||
# Alternative: Use docker compose directly (if make is not available)
|
||||
docker compose -f docker-compose.dev.yaml up
|
||||
```
|
||||
|
||||
**Tip:** Run `make help` to see all available development commands including `make down`, `make logs`, `make app` (shell into backend), and more.
|
||||
|
||||
3. **Access the application:**
|
||||
- Frontend (with HMR): http://localhost:5173
|
||||
- Backend API: http://localhost:8000
|
||||
- Database: localhost:5432
|
||||
|
||||
That's it! Now you can edit code and see changes instantly:
|
||||
- Edit Python files → Backend auto-reloads
|
||||
- Edit Svelte/TypeScript files → Frontend HMR updates in browser
|
||||
- Edit config.toml → Changes apply immediately
|
||||
|
||||
### Setting up the backend development environment (Local)
|
||||
|
||||
1. Clone the repository
|
||||
2. cd into repo root
|
||||
@@ -93,18 +147,21 @@ may be more convenient to run the backend locally too, because then it supports
|
||||
|
||||
9. run the backend with
|
||||
```bash
|
||||
uv run ./media_manager/main.py --reload --port 8000
|
||||
uv run fastapi run media_manager/main.py --reload --port 8000
|
||||
```
|
||||
|
||||
- format code with `uvx ruff format`
|
||||
- lint code with `uvx ruff check`
|
||||
- format code with `uv run ruff format .`
|
||||
- lint code with `uv run ruff check .`
|
||||
|
||||
### Setting up the frontend development environment
|
||||
### Setting up the frontend development environment (Local, Optional)
|
||||
|
||||
**Note:** Using the Docker setup above is recommended. This section is for those who prefer to run the frontend locally
|
||||
outside of Docker.
|
||||
|
||||
1. Clone the repository
|
||||
2. cd into repo root
|
||||
3. cd into `web` directory
|
||||
4. install Node.js and npm if you haven't already, I
|
||||
4. Install Node.js and npm if you haven't already, I
|
||||
used [nvm-windows](https://github.com/coreybutler/nvm-windows?tab=readme-ov-file):
|
||||
```powershell
|
||||
nvm install 24.1.0
|
||||
@@ -114,11 +171,73 @@ may be more convenient to run the backend locally too, because then it supports
|
||||
```powershell
|
||||
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||
```
|
||||
5. Install the dependencies with npm: `npm install`
|
||||
6. Start the frontend development server: `npm run dev`
|
||||
5. Create a `.env` file in the `web` directory:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
Update `PUBLIC_API_URL` if your backend is not at `http://localhost:8000`
|
||||
|
||||
- format the code with `npm run format`
|
||||
- lint the code with `npm run lint`
|
||||
6. Install the dependencies with npm: `npm install`
|
||||
7. Start the frontend development server: `npm run dev`
|
||||
|
||||
**Important:** If running frontend locally, make sure to add `http://localhost:5173` to the `cors_urls` in your backend
|
||||
config file.
|
||||
|
||||
- Format the code with `npm run format`
|
||||
- Lint the code with `npm run lint`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Docker Development Issues
|
||||
|
||||
**Port already in use errors:**
|
||||
- Check if ports 5173, 8000, or 5432 are already in use: `lsof -i :5173` (macOS/Linux) or `netstat -ano | findstr :5173` (Windows)
|
||||
- Stop conflicting services or change ports in `docker-compose.dev.yaml`
|
||||
|
||||
**Container not showing code changes:**
|
||||
- Verify volume mounts are correct in `docker-compose.dev.yaml`
|
||||
- For backend: Ensure `./media_manager:/app/media_manager` is mounted
|
||||
- For frontend: Ensure `./web:/app` is mounted
|
||||
- On Windows: Check that file watching is enabled in Docker Desktop settings
|
||||
|
||||
**Frontend changes not updating:**
|
||||
- Check that the frontend container is running: `make ps` or `docker compose -f docker-compose.dev.yaml ps`
|
||||
- Verify Vite's file watching is working (should see HMR updates in browser console)
|
||||
- Try restarting the frontend container: `docker compose -f docker-compose.dev.yaml restart frontend`
|
||||
|
||||
**Backend changes not reloading:**
|
||||
- Verify `MEDIAMANAGER_MISC__DEVELOPMENT=TRUE` is set in `docker-compose.dev.yaml`
|
||||
- Check backend logs: `make logs ARGS="--follow mediamanager"` or `docker compose -f docker-compose.dev.yaml logs -f mediamanager`
|
||||
- If dependencies changed, rebuild: `docker compose -f docker-compose.dev.yaml build mediamanager`
|
||||
|
||||
**Database migration issues:**
|
||||
- Migrations run automatically on container startup
|
||||
- To run manually: `make app` then `uv run alembic upgrade head`
|
||||
- To create new migration: `make app` then `uv run alembic revision --autogenerate -m "description"`
|
||||
|
||||
**Viewing logs:**
|
||||
- All services: `make logs`
|
||||
- Follow logs in real-time: `make logs ARGS="--follow"`
|
||||
- Specific service: `make logs ARGS="mediamanager --follow"`
|
||||
|
||||
**Interactive debugging:**
|
||||
- Shell into backend: `make app` (or `docker compose -f docker-compose.dev.yaml exec -it mediamanager bash`)
|
||||
- Shell into frontend: `make frontend` (or `docker compose -f docker-compose.dev.yaml exec -it frontend sh`)
|
||||
- Once inside, you can run commands like `uv run alembic upgrade head`, `npm install`, etc.
|
||||
|
||||
**Volume permission issues (Linux):**
|
||||
- Docker containers may create files as root, causing permission issues
|
||||
- Solution: Run containers with your user ID or use Docker's `user:` directive
|
||||
- Alternatively: `sudo chown -R $USER:$USER .` to reclaim ownership
|
||||
|
||||
**Complete reset:**
|
||||
If all else fails, you can completely reset your development environment:
|
||||
```bash
|
||||
make down
|
||||
docker compose -f docker-compose.dev.yaml down -v # Remove volumes
|
||||
docker compose -f docker-compose.dev.yaml build --no-cache # Rebuild without cache
|
||||
make up
|
||||
```
|
||||
|
||||
## Sequence Diagrams
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
[misc]
|
||||
# it's very likely that you need to change this for MediaManager to work
|
||||
frontend_url = "http://localhost:5173/" # note the trailing slash
|
||||
cors_urls = ["http://localhost:8000", "http://localhost:5173"] # note the lack of a trailing slash
|
||||
cors_urls = ["http://localhost:8000", "http://localhost:5173", "http://mediamanager:8000"] # note the lack of a trailing slash
|
||||
|
||||
image_directory = "/data/images"
|
||||
tv_directory = "/data/tv"
|
||||
|
||||
@@ -15,6 +15,7 @@ services:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
target: app
|
||||
args:
|
||||
- VERSION=locally-built
|
||||
- BASE_PATH=
|
||||
@@ -24,12 +25,26 @@ services:
|
||||
- "8000:8000"
|
||||
environment:
|
||||
- CONFIG_DIR=/app/config
|
||||
- MEDIAMANAGER_MISC__DEVELOPMENT=TRUE
|
||||
- DISABLE_FRONTEND_MOUNT=TRUE
|
||||
volumes:
|
||||
#- ./web/build:/app/web/build # this is only needed to test built frontend when developing frontend
|
||||
- ./res/images/:/data/images/
|
||||
- ./res/:/data/
|
||||
- ./res/config/:/app/config/
|
||||
- ./media_manager:/app/media_manager
|
||||
frontend:
|
||||
image: node:24-alpine
|
||||
container_name: mediamanager-frontend-dev
|
||||
working_dir: /app
|
||||
command: sh -c "npm install && npm run dev -- --host 0.0.0.0"
|
||||
ports:
|
||||
- "5173:5173"
|
||||
- "24678:24678"
|
||||
volumes:
|
||||
- ./web:/app
|
||||
depends_on:
|
||||
- mediamanager
|
||||
|
||||
# ----------------------------
|
||||
# Additional services can be uncommented and configured as needed
|
||||
|
||||
@@ -190,6 +190,7 @@ async def lifespan(app: FastAPI):
|
||||
|
||||
BASE_PATH = os.getenv("BASE_PATH", "")
|
||||
FRONTEND_FILES_DIR = os.getenv("FRONTEND_FILES_DIR")
|
||||
DISABLE_FRONTEND_MOUNT = os.getenv("DISABLE_FRONTEND_MOUNT", "").lower() in ["true", "1", "yes"]
|
||||
|
||||
|
||||
app = FastAPI(lifespan=lifespan, root_path=BASE_PATH)
|
||||
@@ -290,7 +291,16 @@ app.mount(
|
||||
)
|
||||
|
||||
app.include_router(api_app)
|
||||
|
||||
# ----------------------------
|
||||
# Frontend mounting (disabled in development)
|
||||
# ----------------------------
|
||||
|
||||
if not DISABLE_FRONTEND_MOUNT:
|
||||
app.mount("/web", StaticFiles(directory=FRONTEND_FILES_DIR, html=True), name="frontend")
|
||||
log.info(f"Mounted frontend at /web from {FRONTEND_FILES_DIR}")
|
||||
else:
|
||||
log.info("Frontend mounting disabled (DISABLE_FRONTEND_MOUNT is set)")
|
||||
|
||||
# ----------------------------
|
||||
# Redirects to frontend
|
||||
@@ -325,7 +335,7 @@ app.add_exception_handler(UniqueViolation, sqlalchemy_integrity_error_handler)
|
||||
|
||||
@app.exception_handler(404)
|
||||
async def not_found_handler(request, exc):
|
||||
if any(
|
||||
if not DISABLE_FRONTEND_MOUNT and any(
|
||||
base_path in ["/web", "/dashboard", "/login"] for base_path in request.url.path
|
||||
):
|
||||
return FileResponse(f"{FRONTEND_FILES_DIR}/404.html")
|
||||
|
||||
13
web/.env.example
Normal file
13
web/.env.example
Normal file
@@ -0,0 +1,13 @@
|
||||
# Frontend Environment Variables
|
||||
# These are set automatically in docker-compose.dev.yaml for development
|
||||
|
||||
# API URL for backend communication
|
||||
# Development: http://localhost:5173
|
||||
# Production: Built into the static files during Docker build
|
||||
PUBLIC_API_URL=http://localhost:5173
|
||||
|
||||
# Base path for routing (leave empty unless using a subdirectory deployment)
|
||||
BASE_PATH=
|
||||
|
||||
# Version string (automatically set during build)
|
||||
PUBLIC_VERSION=dev
|
||||
@@ -4,5 +4,21 @@ import { enhancedImages } from '@sveltejs/enhanced-img';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [tailwindcss(), enhancedImages(), sveltekit()]
|
||||
plugins: [tailwindcss(), enhancedImages(), sveltekit()],
|
||||
server: {
|
||||
host: '0.0.0.0', // Allow external connections (required for Docker)
|
||||
port: 5173,
|
||||
strictPort: true, // Fail if port is already in use
|
||||
watch: {
|
||||
usePolling: true, // Required for file watching in Docker on some systems
|
||||
interval: 100 // Check for changes every 100ms
|
||||
},
|
||||
proxy: {
|
||||
// Proxy API requests to backend container
|
||||
'/api': {
|
||||
target: 'http://mediamanager:8000',
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user