diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a505950 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,32 @@ +# Dependencies +node_modules + +# Build output +dist + +# Git +.git +.github + +# IDE +.idea +.vscode + +# OS +.DS_Store + +# Environment +.env +.env.* + +# Documentation +*.md +license + +# Docker +Dockerfile* +docker-compose*.yml +.dockerignore + +# Other +*.log diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..9dddc97 --- /dev/null +++ b/.env.example @@ -0,0 +1,12 @@ +# Monochrome Docker Configuration +# Copy to .env and edit: cp .env.example .env + +# --- Monochrome --- +MONOCHROME_PORT=3000 +MONOCHROME_DEV_PORT=5173 + +# --- PocketBase (only used with --profile pocketbase) --- +POCKETBASE_PORT=8090 +PB_ADMIN_EMAIL=admin@example.com +PB_ADMIN_PASSWORD=changeme +TZ=UTC diff --git a/.gitignore b/.gitignore index 3d1050b..c1ee8f9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ dist .DS_Store *.local .vite + +# Docker +.env diff --git a/DOCKER.md b/DOCKER.md new file mode 100644 index 0000000..74a4e5d --- /dev/null +++ b/DOCKER.md @@ -0,0 +1,187 @@ +# Docker Deployment Guide + +## Quick Start + +### Monochrome Only + +```bash +docker compose up -d +``` + +Visit `http://localhost:3000` + +### With PocketBase + +```bash +cp .env.example .env +# Edit .env -- set PB_ADMIN_EMAIL and PB_ADMIN_PASSWORD +docker compose --profile pocketbase up -d +``` + +- Monochrome: `http://localhost:3000` +- PocketBase admin: `http://localhost:8090/_/` + +Configure PocketBase collections per [self-hosted-database.md](self-hosted-database.md). + +### Development + +```bash +docker compose --profile dev up -d +``` + +Visit `http://localhost:5173` (hot-reload enabled) + +--- + +## How It Works + +### Profiles + +Docker Compose [profiles](https://docs.docker.com/compose/how-tos/profiles/) control which services start. A service with no profile always runs. A service with a profile only runs when that profile is activated. + +| Command | What starts | +| --------------------------------------------------------- | ------------------------------------ | +| `docker compose up -d` | Monochrome | +| `docker compose --profile pocketbase up -d` | Monochrome + PocketBase | +| `docker compose --profile dev up -d` | Monochrome + Dev server | +| `docker compose --profile dev --profile pocketbase up -d` | Monochrome + Dev server + PocketBase | + +In `docker-compose.yml`, it looks like this: + +```yaml +services: + monochrome: # no profile -- always starts + + pocketbase: + profiles: ['pocketbase'] # opt-in + + monochrome-dev: + profiles: ['dev'] # opt-in +``` + +### Override File + +Docker Compose automatically merges `docker-compose.override.yml` into `docker-compose.yml` if it exists in the same directory. No flags needed. + +This is useful for forks that need to add custom services or configuration (Traefik labels, extra containers, custom networks) without modifying the base `docker-compose.yml`. + +The override file does not exist in the upstream repo, don't search it! + +**Example** -- adding Traefik labels to PocketBase in your fork: + +```yaml +# docker-compose.override.yml +services: + pocketbase: + labels: + - traefik.enable=true + - traefik.http.routers.pocketbase.rule=Host(`pocketbase.example.com`) + - traefik.http.routers.pocketbase.entrypoints=websecure + - traefik.http.routers.pocketbase.tls.certresolver=letsencrypt + - traefik.http.services.pocketbase.loadbalancer.server.port=8090 + networks: + - proxy-network + +networks: + proxy-network: + external: true +``` + +**Example** -- adding a custom service in your fork: + +```yaml +# docker-compose.override.yml +services: + my-custom-api: + image: my-api:latest + restart: unless-stopped + ports: + - '4000:4000' + networks: + - monochrome-network +``` + +Override files can extend existing services (add labels, env vars, networks) and define entirely new services. See the [Docker docs](https://docs.docker.com/compose/how-tos/multiple-compose-files/merge/) for the full merge behavior. + +--- + +## Portainer Deployment + +Portainer can deploy directly from your GitHub fork with auto-updates on push. + +### Setup + +1. In Portainer, go to **Stacks > Add Stack > Repository** +2. Enter your fork URL and branch +3. Compose path: `docker-compose.yml` +4. If your fork has a `docker-compose.override.yml`, Portainer loads it automatically +5. Under **Environment variables**, add: + - `COMPOSE_PROFILES=pocketbase` (to enable PocketBase -- omit if not needed) + - `PB_ADMIN_EMAIL=your@email.com` + - `PB_ADMIN_PASSWORD=your_secure_password` + - Any other variables from `.env.example` +6. Enable **GitOps updates** to auto-redeploy on push + +> **Tip:** `COMPOSE_PROFILES` is a built-in Docker Compose variable. Setting it to `pocketbase` is equivalent to passing `--profile pocketbase` on the command line. + +> **Warning:** The `dev` profile is for **local development only**. It uses volume mounts to enable hot-reload, which requires the source code to be present on the host machine. Do **not** include `dev` in `COMPOSE_PROFILES` on Portainer deployments from GitHub — it will fail because there's no local source code to mount. + +### Fork Workflow + +To add custom services (Traefik, monitoring, etc.) to your fork: + +1. Create `docker-compose.override.yml` in your fork +2. Remove the `docker-compose.override.yml` line from `.gitignore` +3. Commit both changes to your fork +4. Portainer will auto-load the override file alongside the base compose + +When pulling updates from upstream (`git pull upstream main`), there are no conflicts -- the upstream repo does not have an override file. + +--- + +## Common Operations + +```bash +# View logs +docker compose logs -f +docker compose logs -f pocketbase + +# Rebuild after code changes +docker compose up -d --build + +# Stop everything (include all profiles you started) +docker compose --profile pocketbase down + +# Stop and remove volumes (data loss!) +docker compose --profile pocketbase down -v + +# Backup PocketBase data +docker compose exec pocketbase tar czf - /pb_data > backup.tar.gz + +# Restore PocketBase data +docker compose exec pocketbase tar xzf - -C / < backup.tar.gz +``` + +--- + +## Architecture + +### Production (Dockerfile) + +Node.js Alpine image (multi-arch: amd64 + arm64). Installs dependencies, runs `vite build`, then serves the built files with `vite preview` on port 4173. + +### Development (Dockerfile.dev) + +Node.js Alpine image with source code mounted as a volume for hot-reload. + +### Files + +| File | Purpose | In upstream repo | +| ----------------------------- | ----------------------------- | :--------------: | +| `docker-compose.yml` | All services with profiles | Yes | +| `docker-compose.override.yml` | Fork-specific customizations | No | +| `.env.example` | Environment variable template | Yes | +| `.env` | Your local configuration | No | +| `Dockerfile` | Production build | Yes | +| `Dockerfile.dev` | Development build | Yes | +| `.dockerignore` | Build context exclusions | Yes | diff --git a/Dockerfile b/Dockerfile index 602f9fb..2f9995d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,23 +1,25 @@ -# Use Bun canary on Alpine -FROM oven/bun:canary-alpine +# Node Alpine -- multi-arch (amd64 + arm64) +FROM node:lts-alpine -# Set working directory WORKDIR /app -# Copy package files first for caching -COPY package.json bun.lock ./ +# wget is needed for Docker healthcheck +RUN apk add --no-cache wget -# Install all dependencies (including devDeps) -RUN bun install +# Copy package files first for caching +COPY package.json ./ + +# Install dependencies +RUN npm install # Copy the rest of the project COPY . . # Build the project -RUN bun run build +RUN npm run build # Expose Vite preview port EXPOSE 4173 # Run the built project -CMD ["bun", "run", "preview", "--", "--host", "0.0.0.0"] +CMD ["npm", "run", "preview", "--", "--host", "0.0.0.0"] diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 0000000..6158e29 --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,16 @@ +# Development Dockerfile for hot-reloading +# Node Alpine -- multi-arch (amd64 + arm64) +FROM node:lts-alpine + +WORKDIR /app + +# Copy package files first for caching +COPY package.json ./ + +# Install dependencies +RUN npm install + +# Expose Vite dev server port +EXPOSE 5173 + +CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"] diff --git a/README.md b/README.md index f4c5f6d..9542fe2 100644 --- a/README.md +++ b/README.md @@ -107,12 +107,26 @@ For alternative instances, check [INSTANCES.md](INSTANCES.md). NOTE: Accounts wont work on self-hosted instances. -### Prerequisites +### Option 1: Docker (Recommended) + +```bash +git clone https://github.com/monochrome-music/monochrome.git +cd monochrome +docker compose up -d +``` + +Visit `http://localhost:3000` + +For PocketBase, development mode, and advanced setups, see [DOCKER.md](DOCKER.md). + +### Option 2: Manual Installation + +#### Prerequisites - [Node.js](https://nodejs.org/) (Version 20+ or 22+ recommended) - [Bun](https://bun.sh/) or [npm](https://www.npmjs.com/) -### Local Development +#### Local Development 1. **Clone the repository:** @@ -140,7 +154,7 @@ NOTE: Accounts wont work on self-hosted instances. 4. **Open your browser:** Navigate to `http://localhost:5173/` -### Building for Production +#### Building for Production ```bash bun run build diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..3c39b58 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,71 @@ +services: + # Production frontend -- always runs + monochrome: + build: + context: . + dockerfile: Dockerfile + container_name: monochrome + ports: + - '${MONOCHROME_PORT:-3000}:4173' + restart: unless-stopped + networks: + - monochrome-network + healthcheck: + test: ['CMD', 'wget', '--no-verbose', '--tries=1', '--spider', 'http://localhost:4173/'] + interval: 30s + timeout: 3s + retries: 3 + start_period: 5s + + # PocketBase backend -- only starts with: docker compose --profile pocketbase up -d + pocketbase: + image: ghcr.io/muchobien/pocketbase:latest + container_name: monochrome-pocketbase + profiles: + - pocketbase + restart: unless-stopped + environment: + PB_ADMIN_EMAIL: ${PB_ADMIN_EMAIL:-admin@example.com} + PB_ADMIN_PASSWORD: ${PB_ADMIN_PASSWORD:-changeme} + TZ: ${TZ:-UTC} + ports: + - '${POCKETBASE_PORT:-8090}:8090' + volumes: + - pb_data:/pb_data + - pb_public:/pb_public + - pb_hooks:/pb_hooks + command: serve --http=0.0.0.0:8090 + healthcheck: + test: ['CMD', 'wget', '--no-verbose', '--tries=1', '--spider', 'http://localhost:8090/api/health'] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + networks: + - monochrome-network + + # Development server -- only starts with: docker compose --profile dev up -d + monochrome-dev: + build: + context: . + dockerfile: Dockerfile.dev + container_name: monochrome-dev + profiles: + - dev + ports: + - '${MONOCHROME_DEV_PORT:-5173}:5173' + volumes: + - .:/app + - /app/node_modules + command: npm run dev -- --host 0.0.0.0 + networks: + - monochrome-network + +networks: + monochrome-network: + driver: bridge + +volumes: + pb_data: + pb_public: + pb_hooks: