refactor(deploy): merge the three compose files into a base plus overlays
The dev and prod stacks each declared the same nine services, which is how the S3 credentials and the gateway configuration ended up diverging. compose.yml now holds every service with no published port and no domain in it, and the overlays only add what is specific: host ports and pgAdmin for development, the nginx edge and the mounted secrets for production. The applications sit behind the `apps` profile, so a plain `up` still starts infrastructure only. Everything environment-specific moves to the env files: the public URLs, the credentials and the environment name. The SeaweedFS identity file is rendered inside the container from S3_ACCESS_KEY and S3_SECRET_KEY, so the store and the applications can no longer be given different keys.
This commit is contained in:
+10
-4
@@ -15,10 +15,16 @@ artifacts/
|
|||||||
Thumbs.db
|
Thumbs.db
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
# Secrets / local
|
# Environment files (deploy/.env, deploy/.env.prod, web-player/.env, ...)
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
*.env
|
*.env
|
||||||
|
!.env.example
|
||||||
|
!.env.prod.example
|
||||||
|
|
||||||
# Production env and mounted secrets
|
# Deploy secrets and runtime state
|
||||||
.env.prod
|
deploy/keys/
|
||||||
*.env.*
|
|
||||||
deploy/secrets/
|
deploy/secrets/
|
||||||
|
deploy/nginx/certs/
|
||||||
|
deploy/zitadel/.output/
|
||||||
|
deploy/.tmp/
|
||||||
|
|||||||
+44
-23
@@ -1,40 +1,61 @@
|
|||||||
# Dev stack config (deploy/docker-compose.yml).
|
# Development configuration for deploy/compose.yml + compose.dev.yml.
|
||||||
# Copy to deploy/.env and adjust. .env is not versioned (.gitignore ignores *.env).
|
# Copy to deploy/.env (up.ps1 does it for you) and adjust. Not versioned.
|
||||||
|
|
||||||
# PostgreSQL (shared by Musify and Zitadel). Local apps use localhost:59000.
|
DOTNET_ENVIRONMENT=Development
|
||||||
|
|
||||||
|
# --- Public URLs -------------------------------------------------------------
|
||||||
|
# How each service is reached from *outside* Docker. `host.docker.internal`
|
||||||
|
# resolves to the same address from the browser, from apps running on the host
|
||||||
|
# and from inside the containers, which is what makes the OIDC issuer and the
|
||||||
|
# presigned S3 URLs valid in both run modes.
|
||||||
|
PUBLIC_WEB_URL=http://localhost:3000
|
||||||
|
PUBLIC_API_URL=http://localhost:5111
|
||||||
|
PUBLIC_STREAM_URL=http://localhost:8081
|
||||||
|
PUBLIC_AUTH_URL=http://host.docker.internal:8080
|
||||||
|
|
||||||
|
# --- PostgreSQL (shared by Musify and Zitadel) -------------------------------
|
||||||
POSTGRES_USER=postgres
|
POSTGRES_USER=postgres
|
||||||
POSTGRES_PASSWORD=postgres
|
POSTGRES_PASSWORD=postgres
|
||||||
POSTGRES_DB=musify_db
|
POSTGRES_DB=musify_db
|
||||||
POSTGRES_HOST_PORT=59000
|
POSTGRES_HOST_PORT=59000
|
||||||
|
|
||||||
# pgAdmin (Postgres UI). http://localhost:5050
|
# --- Zitadel (OIDC) ----------------------------------------------------------
|
||||||
PGADMIN_DEFAULT_EMAIL=admin@musify.dev
|
# The masterkey MUST be exactly 32 characters. The external domain/port form
|
||||||
PGADMIN_DEFAULT_PASSWORD=admin
|
# the token issuer, so they have to match PUBLIC_AUTH_URL. Changing them needs
|
||||||
PGADMIN_HOST_PORT=5050
|
# a re-init: ./up.ps1 -Destroy; ./up.ps1
|
||||||
|
|
||||||
# RabbitMQ (MassTransit)
|
|
||||||
RABBITMQ_DEFAULT_USER=guest
|
|
||||||
RABBITMQ_DEFAULT_PASS=guest
|
|
||||||
|
|
||||||
# Zitadel (OIDC). The masterkey MUST be exactly 32 characters.
|
|
||||||
ZITADEL_MASTERKEY=MasterkeyNeedsToHave32Characters
|
ZITADEL_MASTERKEY=MasterkeyNeedsToHave32Characters
|
||||||
# The external domain/port forms the token issuer and must match everywhere.
|
|
||||||
# host.docker.internal resolves the same from browser, local apps and containers,
|
|
||||||
# so auth works both locally and full-docker. Changing it needs a re-init
|
|
||||||
# (up.ps1 -Destroy; up.ps1). Admin login: <user>@zitadel.<domain>.
|
|
||||||
ZITADEL_EXTERNAL_DOMAIN=host.docker.internal
|
ZITADEL_EXTERNAL_DOMAIN=host.docker.internal
|
||||||
ZITADEL_EXTERNAL_PORT=8080
|
ZITADEL_EXTERNAL_PORT=8080
|
||||||
|
ZITADEL_EXTERNAL_SECURE=false
|
||||||
|
ZITADEL_DATABASE_PASSWORD=zitadel
|
||||||
|
# Console login: <username>@zitadel.<external domain>
|
||||||
ZITADEL_ADMIN_USERNAME=admin
|
ZITADEL_ADMIN_USERNAME=admin
|
||||||
ZITADEL_ADMIN_PASSWORD=Password1!
|
ZITADEL_ADMIN_PASSWORD=Password1!
|
||||||
|
|
||||||
# SeaweedFS (S3). Must match deploy/seaweedfs/s3.conf.
|
# OIDC clients, filled in by deploy/zitadel/provision.ps1.
|
||||||
|
AUTH_CLIENT_ID=
|
||||||
|
WEB_CLIENT_ID=
|
||||||
|
WEB_CLIENT_SECRET=
|
||||||
|
|
||||||
|
# --- RabbitMQ (MassTransit) --------------------------------------------------
|
||||||
|
RABBITMQ_DEFAULT_USER=guest
|
||||||
|
RABBITMQ_DEFAULT_PASS=guest
|
||||||
|
|
||||||
|
# --- SeaweedFS (S3) ----------------------------------------------------------
|
||||||
S3_ACCESS_KEY=admin_access_key
|
S3_ACCESS_KEY=admin_access_key
|
||||||
S3_SECRET_KEY=admin_secret_key
|
S3_SECRET_KEY=admin_secret_key
|
||||||
S3_BUCKET=webapi-storage
|
S3_BUCKET=webapi-storage
|
||||||
|
S3_PUBLIC_URL=http://host.docker.internal:8333
|
||||||
|
S3_USE_HTTP=true
|
||||||
|
|
||||||
# Host paths used by up.ps1 and the apps overlay.
|
# --- Web player --------------------------------------------------------------
|
||||||
STREAM_KEYS_DIR=D:/weed
|
# Key material for the encrypted session cookie: any long random string.
|
||||||
WORKER_TEMP_DIR=D:/tempsFilesDev
|
SESSION_SECRET=dev-session-secret-change-me-0123456789abcdef
|
||||||
|
# Allows the plain-HTTP Zitadel issuer in a production Node build (dev only).
|
||||||
|
AUTH_ALLOW_INSECURE_HTTP=true
|
||||||
|
|
||||||
# OIDC ClientId, filled in by zitadel/provision.ps1 (used by the docker path).
|
# --- Misc --------------------------------------------------------------------
|
||||||
AUTH_CLIENT_ID=
|
AUTH_REQUIRE_HTTPS=false
|
||||||
|
PGADMIN_DEFAULT_EMAIL=admin@musify.dev
|
||||||
|
PGADMIN_DEFAULT_PASSWORD=admin
|
||||||
|
PGADMIN_HOST_PORT=5050
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# Production configuration for deploy/compose.yml + compose.prod.yml.
|
||||||
|
# Copy to deploy/.env.prod (up.sh does it for you), then:
|
||||||
|
#
|
||||||
|
# 1. replace every `example.com` with your domain,
|
||||||
|
# 2. replace every CHANGE_ME with a strong random value
|
||||||
|
# (`openssl rand -hex 24`; the Zitadel masterkey needs exactly 32 chars),
|
||||||
|
# 3. point these DNS records at the server, all proxied/TLS-terminated the
|
||||||
|
# same way: example.com, www, api, auth, stream, s3.
|
||||||
|
#
|
||||||
|
# Not versioned. Keep it readable only by the deploy user (chmod 600).
|
||||||
|
|
||||||
|
DOTNET_ENVIRONMENT=Production
|
||||||
|
|
||||||
|
# --- Domain ------------------------------------------------------------------
|
||||||
|
# ROOT_DOMAIN drives the nginx vhosts; the URLs below must agree with it.
|
||||||
|
ROOT_DOMAIN=example.com
|
||||||
|
PUBLIC_WEB_URL=https://example.com
|
||||||
|
PUBLIC_API_URL=https://api.example.com
|
||||||
|
PUBLIC_STREAM_URL=https://stream.example.com
|
||||||
|
PUBLIC_AUTH_URL=https://auth.example.com
|
||||||
|
S3_PUBLIC_URL=https://s3.example.com
|
||||||
|
|
||||||
|
# --- PostgreSQL (shared by Musify and Zitadel) -------------------------------
|
||||||
|
POSTGRES_USER=postgres
|
||||||
|
POSTGRES_PASSWORD=CHANGE_ME
|
||||||
|
POSTGRES_DB=musify_db
|
||||||
|
|
||||||
|
# --- Zitadel (OIDC) ----------------------------------------------------------
|
||||||
|
# The masterkey MUST be exactly 32 characters and can never be rotated in place.
|
||||||
|
ZITADEL_MASTERKEY=CHANGE_ME_32_CHARACTERS_EXACTLY_
|
||||||
|
ZITADEL_EXTERNAL_DOMAIN=auth.example.com
|
||||||
|
ZITADEL_EXTERNAL_PORT=443
|
||||||
|
ZITADEL_EXTERNAL_SECURE=true
|
||||||
|
ZITADEL_DATABASE_PASSWORD=CHANGE_ME
|
||||||
|
# Console login: <username>@zitadel.<external domain>
|
||||||
|
ZITADEL_ADMIN_USERNAME=admin
|
||||||
|
ZITADEL_ADMIN_PASSWORD=CHANGE_ME
|
||||||
|
|
||||||
|
# OIDC clients, filled in by deploy/zitadel/provision.sh.
|
||||||
|
AUTH_CLIENT_ID=
|
||||||
|
WEB_CLIENT_ID=
|
||||||
|
WEB_CLIENT_SECRET=
|
||||||
|
|
||||||
|
# --- RabbitMQ (MassTransit) --------------------------------------------------
|
||||||
|
RABBITMQ_DEFAULT_USER=musify
|
||||||
|
RABBITMQ_DEFAULT_PASS=CHANGE_ME
|
||||||
|
|
||||||
|
# --- SeaweedFS (S3) ----------------------------------------------------------
|
||||||
|
S3_ACCESS_KEY=CHANGE_ME
|
||||||
|
S3_SECRET_KEY=CHANGE_ME
|
||||||
|
S3_BUCKET=webapi-storage
|
||||||
|
S3_USE_HTTP=false
|
||||||
|
|
||||||
|
# --- Web player --------------------------------------------------------------
|
||||||
|
# Key material for the encrypted session cookie. Rotating it logs everyone out.
|
||||||
|
SESSION_SECRET=CHANGE_ME
|
||||||
|
AUTH_ALLOW_INSECURE_HTTP=false
|
||||||
|
|
||||||
|
# --- Misc --------------------------------------------------------------------
|
||||||
|
AUTH_REQUIRE_HTTPS=true
|
||||||
|
# Largest request body nginx accepts (audio uploads go through the web player).
|
||||||
|
MAX_UPLOAD_SIZE=1024m
|
||||||
|
# Extra yt-dlp flags for the Worker. The cookie jar avoids YouTube's bot checks;
|
||||||
|
# drop it here if you do not have one: deploy/secrets/ is mounted at /secrets.
|
||||||
|
YTDLP_ADDITIONAL_ARGUMENTS=--cookies /secrets/youtube_cookies.txt --extractor-retries 1 --socket-timeout 15 -N 4
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
name: musify-dev
|
||||||
|
|
||||||
|
# Development overlay: publishes the infrastructure ports to the host so the
|
||||||
|
# .NET apps and the web player can run from the IDE, and adds pgAdmin.
|
||||||
|
#
|
||||||
|
# docker compose -f deploy/compose.yml -f deploy/compose.dev.yml up -d
|
||||||
|
# docker compose -f deploy/compose.yml -f deploy/compose.dev.yml --profile apps up -d --build
|
||||||
|
#
|
||||||
|
# `host.docker.internal` (see ZITADEL_EXTERNAL_DOMAIN / *_PUBLIC_URL in .env)
|
||||||
|
# resolves to the same address from the browser, from apps on the host and from
|
||||||
|
# inside the containers, so the OIDC issuer and the presigned S3 URLs are valid
|
||||||
|
# in both modes. `extra_hosts` gives Linux containers that same name.
|
||||||
|
|
||||||
|
x-host-alias: &host-alias
|
||||||
|
- "${ZITADEL_EXTERNAL_DOMAIN}:host-gateway"
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
ports:
|
||||||
|
- "${POSTGRES_HOST_PORT}:5432"
|
||||||
|
|
||||||
|
zitadel:
|
||||||
|
ports:
|
||||||
|
- "${ZITADEL_EXTERNAL_PORT}:8080"
|
||||||
|
|
||||||
|
rabbitmq:
|
||||||
|
ports:
|
||||||
|
- "5672:5672" # AMQP -> MassTransit
|
||||||
|
- "15672:15672" # management UI
|
||||||
|
|
||||||
|
seaweedfs:
|
||||||
|
ports:
|
||||||
|
- "9333:9333" # master / admin UI
|
||||||
|
- "8888:8888" # filer -> StreamingGateway
|
||||||
|
- "8333:8333" # S3 API -> API, Worker and presigned uploads
|
||||||
|
|
||||||
|
jaeger:
|
||||||
|
ports:
|
||||||
|
- "16686:16686" # UI
|
||||||
|
- "4317:4317" # OTLP gRPC
|
||||||
|
- "4318:4318" # OTLP HTTP
|
||||||
|
|
||||||
|
# Optional Postgres UI: docker compose ... --profile tools up -d
|
||||||
|
pgadmin:
|
||||||
|
profiles: [tools]
|
||||||
|
image: dpage/pgadmin4:latest
|
||||||
|
container_name: musify-pgadmin
|
||||||
|
environment:
|
||||||
|
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL}
|
||||||
|
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD}
|
||||||
|
PGADMIN_CONFIG_SERVER_MODE: "False"
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
ports:
|
||||||
|
- "${PGADMIN_HOST_PORT}:80"
|
||||||
|
volumes:
|
||||||
|
- pgadmin_data:/var/lib/pgadmin
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
api:
|
||||||
|
ports:
|
||||||
|
- "5111:5111"
|
||||||
|
extra_hosts: *host-alias
|
||||||
|
|
||||||
|
worker:
|
||||||
|
extra_hosts: *host-alias
|
||||||
|
|
||||||
|
gateway:
|
||||||
|
ports:
|
||||||
|
- "8081:8081"
|
||||||
|
extra_hosts: *host-alias
|
||||||
|
|
||||||
|
web-player:
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
extra_hosts: *host-alias
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
pgadmin_data:
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
name: musify-prod
|
||||||
|
|
||||||
|
# Production overlay: adds the nginx edge and the host-mounted secrets.
|
||||||
|
# Nothing but nginx publishes a port; every other service is reachable only on
|
||||||
|
# the internal `musify-prod_default` network.
|
||||||
|
#
|
||||||
|
# docker compose --env-file deploy/.env.prod \
|
||||||
|
# -f deploy/compose.yml -f deploy/compose.prod.yml --profile apps up -d --build
|
||||||
|
#
|
||||||
|
# Use ./up.sh, which also generates the stream keys, provisions Zitadel and
|
||||||
|
# applies the database migrations.
|
||||||
|
|
||||||
|
services:
|
||||||
|
worker:
|
||||||
|
# yt-dlp reads the YouTube cookie jar from here (see YTDLP_ADDITIONAL_ARGUMENTS).
|
||||||
|
volumes:
|
||||||
|
- ./secrets:/secrets:ro
|
||||||
|
|
||||||
|
nginx:
|
||||||
|
image: nginx:1.27-alpine
|
||||||
|
container_name: musify-nginx
|
||||||
|
depends_on:
|
||||||
|
- api
|
||||||
|
- gateway
|
||||||
|
- zitadel
|
||||||
|
- web-player
|
||||||
|
environment:
|
||||||
|
# The entrypoint renders /etc/nginx/templates/*.template with envsubst.
|
||||||
|
# The filter keeps it from eating nginx's own $host / $remote_addr.
|
||||||
|
MUSIFY_ROOT_DOMAIN: ${ROOT_DOMAIN}
|
||||||
|
MUSIFY_MAX_UPLOAD_SIZE: ${MAX_UPLOAD_SIZE:-1024m}
|
||||||
|
NGINX_ENVSUBST_FILTER: ^MUSIFY_
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
volumes:
|
||||||
|
- ./nginx/templates:/etc/nginx/templates:ro
|
||||||
|
- ./nginx/snippets:/etc/nginx/snippets:ro
|
||||||
|
- ./nginx/certs:/etc/nginx/certs:ro
|
||||||
|
restart: unless-stopped
|
||||||
@@ -0,0 +1,277 @@
|
|||||||
|
# Musify — base stack, shared by development and production.
|
||||||
|
#
|
||||||
|
# This file publishes no host ports and hardcodes no domain: everything that
|
||||||
|
# differs between environments comes from the .env file (see .env.example) or
|
||||||
|
# from the overlay that is merged on top of it.
|
||||||
|
#
|
||||||
|
# dev docker compose -f deploy/compose.yml -f deploy/compose.dev.yml up -d
|
||||||
|
# prod docker compose --env-file deploy/.env.prod -f deploy/compose.yml -f deploy/compose.prod.yml up -d
|
||||||
|
#
|
||||||
|
# The four application services sit behind the `apps` profile, so a plain `up`
|
||||||
|
# starts only the infrastructure (the usual dev setup: apps from the IDE).
|
||||||
|
# Add `--profile apps` to run them in Docker. Use ./up.ps1 (dev) or ./up.sh
|
||||||
|
# (prod) instead of calling compose by hand.
|
||||||
|
|
||||||
|
x-app-depends: &app-depends
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
rabbitmq:
|
||||||
|
condition: service_healthy
|
||||||
|
seaweedfs:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
# Backend settings that are identical for the API and the Worker. ASP.NET Core
|
||||||
|
# maps `__` to `:`, so these override the matching AppSettings.json sections.
|
||||||
|
x-backend-env: &backend-env
|
||||||
|
Database__ConnectionString: "Host=postgres;Port=5432;Database=${POSTGRES_DB};Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}"
|
||||||
|
MassTransit__Address: "rabbitmq://rabbitmq:5672"
|
||||||
|
MassTransit__Username: ${RABBITMQ_DEFAULT_USER}
|
||||||
|
MassTransit__Password: ${RABBITMQ_DEFAULT_PASS}
|
||||||
|
# The public S3 URL, not the internal one: the same address is used to sign
|
||||||
|
# the presigned upload/download URLs the browser has to be able to reach.
|
||||||
|
InfrastructureStorage__Address: ${S3_PUBLIC_URL}
|
||||||
|
InfrastructureStorage__AccessKey: ${S3_ACCESS_KEY}
|
||||||
|
InfrastructureStorage__SecretAccessKey: ${S3_SECRET_KEY}
|
||||||
|
InfrastructureStorage__ForcePathStyle: "true"
|
||||||
|
InfrastructureStorage__UseHttp: ${S3_USE_HTTP}
|
||||||
|
ApplicationStorage__Bucket: ${S3_BUCKET}
|
||||||
|
OpenTelemetry__OtlpEndpoint: "http://jaeger:4317"
|
||||||
|
|
||||||
|
services:
|
||||||
|
# --- Infrastructure -------------------------------------------------------
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: postgres:17-alpine
|
||||||
|
container_name: musify-postgres
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB}
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# OIDC provider. `start-from-init` runs init + setup (both idempotent) and
|
||||||
|
# then starts, so the same command works on a cold and on a warm volume.
|
||||||
|
# It reuses the Postgres above: the admin user creates the `zitadel` database
|
||||||
|
# and role on first start.
|
||||||
|
zitadel:
|
||||||
|
image: ghcr.io/zitadel/zitadel:latest
|
||||||
|
container_name: musify-zitadel
|
||||||
|
command: start-from-init --masterkeyFromEnv --tlsMode disabled
|
||||||
|
environment:
|
||||||
|
ZITADEL_MASTERKEY: ${ZITADEL_MASTERKEY}
|
||||||
|
# The external domain/port forms the token issuer and must be identical
|
||||||
|
# for the browser, the apps and the containers (Zitadel routes by Host).
|
||||||
|
ZITADEL_EXTERNALSECURE: ${ZITADEL_EXTERNAL_SECURE}
|
||||||
|
ZITADEL_EXTERNALDOMAIN: ${ZITADEL_EXTERNAL_DOMAIN}
|
||||||
|
ZITADEL_EXTERNALPORT: ${ZITADEL_EXTERNAL_PORT}
|
||||||
|
ZITADEL_TLS_ENABLED: "false"
|
||||||
|
ZITADEL_DATABASE_POSTGRES_HOST: postgres
|
||||||
|
ZITADEL_DATABASE_POSTGRES_PORT: 5432
|
||||||
|
ZITADEL_DATABASE_POSTGRES_DATABASE: zitadel
|
||||||
|
ZITADEL_DATABASE_POSTGRES_USER_USERNAME: zitadel
|
||||||
|
ZITADEL_DATABASE_POSTGRES_USER_PASSWORD: ${ZITADEL_DATABASE_PASSWORD}
|
||||||
|
ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE: disable
|
||||||
|
ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME: ${POSTGRES_USER}
|
||||||
|
ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE: disable
|
||||||
|
# Initial console admin. Login: <username>@zitadel.<external domain>
|
||||||
|
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_USERNAME: ${ZITADEL_ADMIN_USERNAME}
|
||||||
|
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORD: ${ZITADEL_ADMIN_PASSWORD}
|
||||||
|
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORDCHANGEREQUIRED: "false"
|
||||||
|
# Service account + PAT written on init; the provisioning script uses it
|
||||||
|
# to create the Musify project and its OIDC apps.
|
||||||
|
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_USERNAME: zitadel-admin-sa
|
||||||
|
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_NAME: Admin Service Account
|
||||||
|
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_PAT_EXPIRATIONDATE: "2099-01-01T00:00:00Z"
|
||||||
|
ZITADEL_FIRSTINSTANCE_PATPATH: /output/admin-sa.pat
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
volumes:
|
||||||
|
- ./zitadel/.output:/output
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
rabbitmq:
|
||||||
|
image: rabbitmq:4-management-alpine
|
||||||
|
container_name: musify-rabbitmq
|
||||||
|
environment:
|
||||||
|
RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER}
|
||||||
|
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS}
|
||||||
|
volumes:
|
||||||
|
- rabbitmq_data:/var/lib/rabbitmq
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# All-in-one object store: master + volume + filer + S3 gateway.
|
||||||
|
# The S3 identity file is rendered from the environment on start so the
|
||||||
|
# credentials can never drift from the ones handed to the apps.
|
||||||
|
seaweedfs:
|
||||||
|
image: chrislusf/seaweedfs:latest
|
||||||
|
container_name: musify-seaweedfs
|
||||||
|
entrypoint: ["/bin/sh", "-c"]
|
||||||
|
command:
|
||||||
|
- |
|
||||||
|
cat > /tmp/s3.conf <<'JSON'
|
||||||
|
{
|
||||||
|
"identities": [
|
||||||
|
{
|
||||||
|
"name": "musify",
|
||||||
|
"credentials": [{ "accessKey": "${S3_ACCESS_KEY}", "secretKey": "${S3_SECRET_KEY}" }],
|
||||||
|
"actions": ["Admin", "Read", "Write"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
JSON
|
||||||
|
exec weed server \
|
||||||
|
-ip.bind=0.0.0.0 \
|
||||||
|
-dir=/data \
|
||||||
|
-master.volumeSizeLimitMB=1024 \
|
||||||
|
-volume.max=0 \
|
||||||
|
-filer \
|
||||||
|
-s3 \
|
||||||
|
-s3.config=/tmp/s3.conf
|
||||||
|
volumes:
|
||||||
|
- seaweedfs_data:/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "-q", "-O", "-", "http://localhost:9333/cluster/healthz"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# One-shot: SeaweedFS does not auto-create buckets.
|
||||||
|
seaweedfs-init:
|
||||||
|
image: amazon/aws-cli:latest
|
||||||
|
container_name: musify-seaweedfs-init
|
||||||
|
depends_on:
|
||||||
|
seaweedfs:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
AWS_ACCESS_KEY_ID: ${S3_ACCESS_KEY}
|
||||||
|
AWS_SECRET_ACCESS_KEY: ${S3_SECRET_KEY}
|
||||||
|
AWS_DEFAULT_REGION: us-east-1
|
||||||
|
entrypoint: ["/bin/sh", "-c"]
|
||||||
|
command:
|
||||||
|
- |
|
||||||
|
aws --endpoint-url http://seaweedfs:8333 s3 mb s3://${S3_BUCKET} 2>/dev/null || true
|
||||||
|
aws --endpoint-url http://seaweedfs:8333 s3 ls
|
||||||
|
restart: "no"
|
||||||
|
|
||||||
|
# OTLP collector + trace UI (MassTransit / EF Core / HTTP spans).
|
||||||
|
jaeger:
|
||||||
|
image: jaegertracing/all-in-one:latest
|
||||||
|
container_name: musify-jaeger
|
||||||
|
environment:
|
||||||
|
COLLECTOR_OTLP_ENABLED: "true"
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# --- Applications (profile: apps) -----------------------------------------
|
||||||
|
|
||||||
|
api:
|
||||||
|
profiles: [apps]
|
||||||
|
build:
|
||||||
|
context: ../backend
|
||||||
|
dockerfile: Musify.Api.Dockerfile
|
||||||
|
container_name: musify-api
|
||||||
|
depends_on: *app-depends
|
||||||
|
environment:
|
||||||
|
<<: *backend-env
|
||||||
|
ASPNETCORE_ENVIRONMENT: ${DOTNET_ENVIRONMENT}
|
||||||
|
ASPNETCORE_URLS: http://+:5111
|
||||||
|
StreamGateway__PublicBaseUrl: ${PUBLIC_STREAM_URL}
|
||||||
|
StreamTicket__PrivateKeyPath: /keys/stream_private.pem
|
||||||
|
Workers__Routes__TemporaryFilesDirectory: /tmp/musify
|
||||||
|
# Client id and secret are written to the .env file by the Zitadel
|
||||||
|
# provisioning script (deploy/zitadel/provision.*).
|
||||||
|
Authentication__MetadataAddress: "${PUBLIC_AUTH_URL}/.well-known/openid-configuration"
|
||||||
|
Authentication__IssuerAddress: ${PUBLIC_AUTH_URL}
|
||||||
|
Authentication__ClientId: ${AUTH_CLIENT_ID}
|
||||||
|
Authentication__AudienceAddress: ${AUTH_CLIENT_ID}
|
||||||
|
Authentication__AuthorizationEndpoint: "${PUBLIC_AUTH_URL}/oauth/v2/authorize?prompt=login"
|
||||||
|
Authentication__TokenEndpoint: "${PUBLIC_AUTH_URL}/oauth/v2/token"
|
||||||
|
Authentication__ScalarRedirectUri: "${PUBLIC_API_URL}/scalar/"
|
||||||
|
Authentication__Scopes__0: openid
|
||||||
|
Authentication__Scopes__1: profile
|
||||||
|
Authentication__Scopes__2: email
|
||||||
|
Authentication__RequireHttpsMetadata: ${AUTH_REQUIRE_HTTPS}
|
||||||
|
volumes:
|
||||||
|
- ./keys:/keys:ro
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
worker:
|
||||||
|
profiles: [apps]
|
||||||
|
build:
|
||||||
|
context: ../backend
|
||||||
|
dockerfile: Musify.Worker.Dockerfile
|
||||||
|
container_name: musify-worker
|
||||||
|
depends_on: *app-depends
|
||||||
|
environment:
|
||||||
|
<<: *backend-env
|
||||||
|
DOTNET_ENVIRONMENT: ${DOTNET_ENVIRONMENT}
|
||||||
|
AudioTranscoder__Routes__WorkingDirectory: /tmp/musify
|
||||||
|
Workers__Routes__TemporaryFilesDirectory: /tmp/musify
|
||||||
|
YtDlp__AdditionalArguments: ${YTDLP_ADDITIONAL_ARGUMENTS:-}
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# YARP in front of the SeaweedFS filer: validates the RS256 stream ticket and
|
||||||
|
# streams the bytes, so audio never goes through the API.
|
||||||
|
gateway:
|
||||||
|
profiles: [apps]
|
||||||
|
build:
|
||||||
|
context: ../backend
|
||||||
|
dockerfile: Musify.StreamingGateway.Dockerfile
|
||||||
|
container_name: musify-gateway
|
||||||
|
depends_on:
|
||||||
|
seaweedfs:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
ASPNETCORE_ENVIRONMENT: ${DOTNET_ENVIRONMENT}
|
||||||
|
ASPNETCORE_URLS: http://+:8081
|
||||||
|
StreamTicket__PublicKeyPath: /keys/stream_public.pem
|
||||||
|
ReverseProxy__Clusters__seaweed-filer__Destinations__filer__Address: "http://seaweedfs:8888/"
|
||||||
|
ReverseProxy__Routes__media__Transforms__0__PathPattern: "/buckets/${S3_BUCKET}/{**rest}"
|
||||||
|
Cors__AllowedOrigins__0: ${PUBLIC_WEB_URL}
|
||||||
|
volumes:
|
||||||
|
- ./keys:/keys:ro
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
web-player:
|
||||||
|
profiles: [apps]
|
||||||
|
build:
|
||||||
|
context: ../web-player
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: musify-web-player
|
||||||
|
depends_on:
|
||||||
|
- api
|
||||||
|
environment:
|
||||||
|
NODE_ENV: production
|
||||||
|
PORT: 3000
|
||||||
|
ORIGIN: ${PUBLIC_WEB_URL}
|
||||||
|
ZITADEL_ISSUER: ${PUBLIC_AUTH_URL}
|
||||||
|
ZITADEL_CLIENT_ID: ${WEB_CLIENT_ID}
|
||||||
|
ZITADEL_CLIENT_SECRET: ${WEB_CLIENT_SECRET}
|
||||||
|
AUTH_REDIRECT_URI: "${PUBLIC_WEB_URL}/auth/callback"
|
||||||
|
AUTH_POST_LOGOUT_URI: "${PUBLIC_WEB_URL}/"
|
||||||
|
AUTH_ALLOW_INSECURE_HTTP: ${AUTH_ALLOW_INSECURE_HTTP}
|
||||||
|
SESSION_SECRET: ${SESSION_SECRET}
|
||||||
|
API_BASE_URL: http://api:5111
|
||||||
|
# Presigned PUTs are signed for the public S3 host; the container sends
|
||||||
|
# them straight to SeaweedFS instead of going back out through the edge.
|
||||||
|
S3_UPLOAD_ENDPOINT: http://seaweedfs:8333
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
|
rabbitmq_data:
|
||||||
|
seaweedfs_data:
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
name: musify-dev
|
|
||||||
|
|
||||||
# Optional overlay: runs the .NET apps in Docker. Merges with docker-compose.yml.
|
|
||||||
#
|
|
||||||
# docker compose -f deploy/docker-compose.yml -f deploy/docker-compose.apps.yml up -d --build
|
|
||||||
#
|
|
||||||
# For active development prefer infra in Docker + apps from the IDE (easier to
|
|
||||||
# debug). Auth works in both modes: ZITADEL_EXTERNAL_DOMAIN=host.docker.internal
|
|
||||||
# resolves the same from the browser, local apps and containers, so the token
|
|
||||||
# issuer matches everywhere (containers reach the host via extra_hosts).
|
|
||||||
|
|
||||||
x-backend-depends: &backend-depends
|
|
||||||
postgres:
|
|
||||||
condition: service_healthy
|
|
||||||
rabbitmq:
|
|
||||||
condition: service_healthy
|
|
||||||
seaweedfs:
|
|
||||||
condition: service_healthy
|
|
||||||
|
|
||||||
x-backend-env: &backend-env
|
|
||||||
Database__ConnectionString: "Host=postgres;Port=5432;Database=${POSTGRES_DB};Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}"
|
|
||||||
MassTransit__Address: "rabbitmq://rabbitmq:5672"
|
|
||||||
MassTransit__Username: ${RABBITMQ_DEFAULT_USER}
|
|
||||||
MassTransit__Password: ${RABBITMQ_DEFAULT_PASS}
|
|
||||||
InfrastructureStorage__Address: "http://seaweedfs:8333"
|
|
||||||
InfrastructureStorage__AccessKey: ${S3_ACCESS_KEY}
|
|
||||||
InfrastructureStorage__SecretAccessKey: ${S3_SECRET_KEY}
|
|
||||||
InfrastructureStorage__ForcePathStyle: "true"
|
|
||||||
InfrastructureStorage__UseHttp: "true"
|
|
||||||
ApplicationStorage__Bucket: ${S3_BUCKET}
|
|
||||||
OpenTelemetry__OtlpEndpoint: "http://jaeger:4317"
|
|
||||||
|
|
||||||
services:
|
|
||||||
api:
|
|
||||||
build:
|
|
||||||
context: ../backend
|
|
||||||
dockerfile: Musify.Api.Dockerfile
|
|
||||||
container_name: musify-api
|
|
||||||
depends_on: *backend-depends
|
|
||||||
environment:
|
|
||||||
<<: *backend-env
|
|
||||||
ASPNETCORE_ENVIRONMENT: Development
|
|
||||||
ASPNETCORE_URLS: http://+:5111
|
|
||||||
StreamGateway__PublicBaseUrl: "http://localhost:8081"
|
|
||||||
StreamTicket__PrivateKeyPath: "/keys/stream_private.pem"
|
|
||||||
# Auth values provisioned by zitadel/provision.ps1 (AUTH_CLIENT_ID in .env).
|
|
||||||
Authentication__MetadataAddress: "http://${ZITADEL_EXTERNAL_DOMAIN}:${ZITADEL_EXTERNAL_PORT}/.well-known/openid-configuration"
|
|
||||||
Authentication__IssuerAddress: "http://${ZITADEL_EXTERNAL_DOMAIN}:${ZITADEL_EXTERNAL_PORT}"
|
|
||||||
Authentication__ClientId: "${AUTH_CLIENT_ID}"
|
|
||||||
Authentication__AudienceAddress: "${AUTH_CLIENT_ID}"
|
|
||||||
Authentication__AuthorizationEndpoint: "http://${ZITADEL_EXTERNAL_DOMAIN}:${ZITADEL_EXTERNAL_PORT}/oauth/v2/authorize?prompt=login"
|
|
||||||
Authentication__TokenEndpoint: "http://${ZITADEL_EXTERNAL_DOMAIN}:${ZITADEL_EXTERNAL_PORT}/oauth/v2/token"
|
|
||||||
Authentication__ScalarRedirectUri: "http://localhost:5111/scalar/"
|
|
||||||
Authentication__Scopes__0: "openid"
|
|
||||||
Authentication__Scopes__1: "profile"
|
|
||||||
Authentication__Scopes__2: "email"
|
|
||||||
Authentication__RequireHttpsMetadata: "false"
|
|
||||||
extra_hosts:
|
|
||||||
- "${ZITADEL_EXTERNAL_DOMAIN}:host-gateway"
|
|
||||||
volumes:
|
|
||||||
- "${STREAM_KEYS_DIR:-D:/weed}:/keys:ro"
|
|
||||||
ports:
|
|
||||||
- "5111:5111"
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
worker:
|
|
||||||
build:
|
|
||||||
context: ../backend
|
|
||||||
dockerfile: Musify.Worker.Dockerfile
|
|
||||||
container_name: musify-worker
|
|
||||||
depends_on: *backend-depends
|
|
||||||
environment:
|
|
||||||
<<: *backend-env
|
|
||||||
DOTNET_ENVIRONMENT: Development
|
|
||||||
AudioTranscoder__Routes__WorkingDirectory: "/tmp/musify"
|
|
||||||
Workers__Routes__TemporaryFilesDirectory: "/tmp/musify"
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
gateway:
|
|
||||||
build:
|
|
||||||
context: ../backend
|
|
||||||
dockerfile: Musify.StreamingGateway.Dockerfile
|
|
||||||
container_name: musify-gateway
|
|
||||||
depends_on:
|
|
||||||
seaweedfs:
|
|
||||||
condition: service_healthy
|
|
||||||
environment:
|
|
||||||
ASPNETCORE_ENVIRONMENT: Development
|
|
||||||
ASPNETCORE_URLS: http://+:8081
|
|
||||||
StreamTicket__PublicKeyPath: "/keys/stream_public.pem"
|
|
||||||
ReverseProxy__Clusters__seaweed-filer__Destinations__filer__Address: "http://seaweedfs:8888/"
|
|
||||||
volumes:
|
|
||||||
- "${STREAM_KEYS_DIR:-D:/weed}:/keys:ro"
|
|
||||||
ports:
|
|
||||||
- "8081:8081"
|
|
||||||
restart: unless-stopped
|
|
||||||
@@ -1,273 +0,0 @@
|
|||||||
name: musify-prod
|
|
||||||
|
|
||||||
# Production stack for the VPS (ikerdemo.cat). Everything runs on the internal
|
|
||||||
# `musify-prod_default` network; only nginx publishes ports to the host (80/443).
|
|
||||||
# Config in deploy/.env.prod.
|
|
||||||
#
|
|
||||||
# docker compose --env-file deploy/.env.prod -f deploy/docker-compose.prod.yml up -d --build
|
|
||||||
|
|
||||||
x-backend-depends: &backend-depends
|
|
||||||
postgres:
|
|
||||||
condition: service_healthy
|
|
||||||
rabbitmq:
|
|
||||||
condition: service_healthy
|
|
||||||
seaweedfs:
|
|
||||||
condition: service_healthy
|
|
||||||
|
|
||||||
x-backend-env: &backend-env
|
|
||||||
Database__ConnectionString: "Host=postgres;Port=5432;Database=${POSTGRES_DB};Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}"
|
|
||||||
MassTransit__Address: "rabbitmq://rabbitmq:5672"
|
|
||||||
MassTransit__Username: ${RABBITMQ_DEFAULT_USER}
|
|
||||||
MassTransit__Password: ${RABBITMQ_DEFAULT_PASS}
|
|
||||||
InfrastructureStorage__Address: "https://s3.ikerdemo.cat"
|
|
||||||
InfrastructureStorage__AccessKey: ${S3_ACCESS_KEY}
|
|
||||||
InfrastructureStorage__SecretAccessKey: ${S3_SECRET_KEY}
|
|
||||||
InfrastructureStorage__ForcePathStyle: "true"
|
|
||||||
InfrastructureStorage__UseHttp: "false"
|
|
||||||
ApplicationStorage__Bucket: ${S3_BUCKET}
|
|
||||||
OpenTelemetry__OtlpEndpoint: "http://jaeger:4317"
|
|
||||||
|
|
||||||
x-zitadel-db-env: &zitadel-db-env
|
|
||||||
ZITADEL_DATABASE_POSTGRES_HOST: postgres
|
|
||||||
ZITADEL_DATABASE_POSTGRES_PORT: 5432
|
|
||||||
ZITADEL_DATABASE_POSTGRES_DATABASE: zitadel
|
|
||||||
ZITADEL_DATABASE_POSTGRES_USER_USERNAME: zitadel
|
|
||||||
ZITADEL_DATABASE_POSTGRES_USER_PASSWORD: zitadel
|
|
||||||
ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE: disable
|
|
||||||
ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME: ${POSTGRES_USER}
|
|
||||||
ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD: ${POSTGRES_PASSWORD}
|
|
||||||
ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE: disable
|
|
||||||
|
|
||||||
services:
|
|
||||||
postgres:
|
|
||||||
image: postgres:17-alpine
|
|
||||||
container_name: musify-postgres
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: ${POSTGRES_USER}
|
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
|
||||||
POSTGRES_DB: ${POSTGRES_DB}
|
|
||||||
volumes:
|
|
||||||
- postgres_data:/var/lib/postgresql/data
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 10
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
zitadel-init:
|
|
||||||
image: ghcr.io/zitadel/zitadel:latest
|
|
||||||
container_name: musify-zitadel-init
|
|
||||||
command: 'init'
|
|
||||||
environment:
|
|
||||||
<<: *zitadel-db-env
|
|
||||||
depends_on:
|
|
||||||
postgres:
|
|
||||||
condition: service_healthy
|
|
||||||
restart: "no"
|
|
||||||
|
|
||||||
zitadel-setup:
|
|
||||||
image: ghcr.io/zitadel/zitadel:latest
|
|
||||||
container_name: musify-zitadel-setup
|
|
||||||
command: 'setup --masterkeyFromEnv --tlsMode disabled'
|
|
||||||
environment:
|
|
||||||
<<: *zitadel-db-env
|
|
||||||
ZITADEL_MASTERKEY: ${ZITADEL_MASTERKEY}
|
|
||||||
ZITADEL_EXTERNALSECURE: "true"
|
|
||||||
ZITADEL_EXTERNALDOMAIN: ${ZITADEL_EXTERNAL_DOMAIN}
|
|
||||||
ZITADEL_EXTERNALPORT: ${ZITADEL_EXTERNAL_PORT}
|
|
||||||
ZITADEL_TLS_ENABLED: "false"
|
|
||||||
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_USERNAME: ${ZITADEL_ADMIN_USERNAME}
|
|
||||||
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORD: ${ZITADEL_ADMIN_PASSWORD}
|
|
||||||
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORDCHANGEREQUIRED: "false"
|
|
||||||
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_USERNAME: zitadel-admin-sa
|
|
||||||
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_NAME: Admin Service Account
|
|
||||||
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_PAT_EXPIRATIONDATE: "2099-01-01T00:00:00Z"
|
|
||||||
ZITADEL_FIRSTINSTANCE_PATPATH: /output/admin-sa.pat
|
|
||||||
depends_on:
|
|
||||||
zitadel-init:
|
|
||||||
condition: service_completed_successfully
|
|
||||||
volumes:
|
|
||||||
- ./zitadel/.output:/output
|
|
||||||
restart: "no"
|
|
||||||
|
|
||||||
zitadel:
|
|
||||||
image: ghcr.io/zitadel/zitadel:latest
|
|
||||||
container_name: musify-zitadel
|
|
||||||
command: 'start --masterkeyFromEnv --tlsMode disabled'
|
|
||||||
environment:
|
|
||||||
<<: *zitadel-db-env
|
|
||||||
ZITADEL_MASTERKEY: ${ZITADEL_MASTERKEY}
|
|
||||||
ZITADEL_EXTERNALSECURE: "true"
|
|
||||||
ZITADEL_EXTERNALDOMAIN: ${ZITADEL_EXTERNAL_DOMAIN}
|
|
||||||
ZITADEL_EXTERNALPORT: ${ZITADEL_EXTERNAL_PORT}
|
|
||||||
ZITADEL_TLS_ENABLED: "false"
|
|
||||||
depends_on:
|
|
||||||
zitadel-setup:
|
|
||||||
condition: service_completed_successfully
|
|
||||||
volumes:
|
|
||||||
- ./zitadel/.output:/output
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
rabbitmq:
|
|
||||||
image: rabbitmq:4-management-alpine
|
|
||||||
container_name: musify-rabbitmq
|
|
||||||
environment:
|
|
||||||
RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER}
|
|
||||||
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS}
|
|
||||||
volumes:
|
|
||||||
- rabbitmq_data:/var/lib/rabbitmq
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 10
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
seaweedfs:
|
|
||||||
image: chrislusf/seaweedfs:latest
|
|
||||||
container_name: musify-seaweedfs
|
|
||||||
command: >
|
|
||||||
server
|
|
||||||
-ip.bind=0.0.0.0
|
|
||||||
-dir=/data
|
|
||||||
-master.volumeSizeLimitMB=1024
|
|
||||||
-volume.max=0
|
|
||||||
-filer
|
|
||||||
-s3
|
|
||||||
-s3.config=/etc/seaweedfs/s3.conf
|
|
||||||
volumes:
|
|
||||||
- seaweedfs_data:/data
|
|
||||||
- ./seaweedfs/s3.conf:/etc/seaweedfs/s3.conf:ro
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "wget", "-q", "-O", "-", "http://localhost:9333/cluster/healthz"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 10
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
seaweedfs-init:
|
|
||||||
image: amazon/aws-cli:latest
|
|
||||||
container_name: musify-seaweedfs-init
|
|
||||||
depends_on:
|
|
||||||
seaweedfs:
|
|
||||||
condition: service_healthy
|
|
||||||
environment:
|
|
||||||
AWS_ACCESS_KEY_ID: ${S3_ACCESS_KEY}
|
|
||||||
AWS_SECRET_ACCESS_KEY: ${S3_SECRET_KEY}
|
|
||||||
AWS_DEFAULT_REGION: us-east-1
|
|
||||||
entrypoint: ["/bin/sh", "-c"]
|
|
||||||
command:
|
|
||||||
- |
|
|
||||||
aws --endpoint-url http://seaweedfs:8333 s3 mb s3://${S3_BUCKET} 2>/dev/null || true
|
|
||||||
aws --endpoint-url http://seaweedfs:8333 s3 ls
|
|
||||||
restart: "no"
|
|
||||||
|
|
||||||
jaeger:
|
|
||||||
image: jaegertracing/all-in-one:latest
|
|
||||||
container_name: musify-jaeger
|
|
||||||
environment:
|
|
||||||
COLLECTOR_OTLP_ENABLED: "true"
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
api:
|
|
||||||
build:
|
|
||||||
context: ../backend
|
|
||||||
dockerfile: Musify.Api.Dockerfile
|
|
||||||
container_name: musify-api
|
|
||||||
depends_on: *backend-depends
|
|
||||||
environment:
|
|
||||||
<<: *backend-env
|
|
||||||
ASPNETCORE_ENVIRONMENT: Production
|
|
||||||
ASPNETCORE_URLS: http://+:5111
|
|
||||||
StreamGateway__PublicBaseUrl: "https://${STREAM_DOMAIN}"
|
|
||||||
StreamTicket__PrivateKeyPath: "/keys/stream_private.pem"
|
|
||||||
Authentication__MetadataAddress: "https://${AUTH_DOMAIN}/.well-known/openid-configuration"
|
|
||||||
Authentication__IssuerAddress: "https://${AUTH_DOMAIN}"
|
|
||||||
Authentication__ClientId: "${AUTH_CLIENT_ID}"
|
|
||||||
Authentication__AudienceAddress: "${AUTH_CLIENT_ID}"
|
|
||||||
Authentication__AuthorizationEndpoint: "https://${AUTH_DOMAIN}/oauth/v2/authorize?prompt=login"
|
|
||||||
Authentication__TokenEndpoint: "https://${AUTH_DOMAIN}/oauth/v2/token"
|
|
||||||
Authentication__ScalarRedirectUri: "https://${API_DOMAIN}/scalar/"
|
|
||||||
Authentication__Scopes__0: "openid"
|
|
||||||
Authentication__Scopes__1: "profile"
|
|
||||||
Authentication__Scopes__2: "email"
|
|
||||||
Authentication__RequireHttpsMetadata: "true"
|
|
||||||
YtDlp__AdditionalArguments: "--cookies /secrets/youtube_cookies.txt --extractor-retries 1 --socket-timeout 15"
|
|
||||||
volumes:
|
|
||||||
- ./keys:/keys:ro
|
|
||||||
- ./secrets/youtube_cookies.txt:/secrets/youtube_cookies.txt
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
worker:
|
|
||||||
build:
|
|
||||||
context: ../backend
|
|
||||||
dockerfile: Musify.Worker.Dockerfile
|
|
||||||
container_name: musify-worker
|
|
||||||
depends_on: *backend-depends
|
|
||||||
environment:
|
|
||||||
<<: *backend-env
|
|
||||||
DOTNET_ENVIRONMENT: Production
|
|
||||||
AudioTranscoder__Routes__WorkingDirectory: "/tmp/musify"
|
|
||||||
Workers__Routes__TemporaryFilesDirectory: "/tmp/musify"
|
|
||||||
YtDlp__AdditionalArguments: "--cookies /secrets/youtube_cookies.txt --extractor-retries 1 --socket-timeout 15 -N 4"
|
|
||||||
volumes:
|
|
||||||
- ./secrets/youtube_cookies.txt:/secrets/youtube_cookies.txt
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
gateway:
|
|
||||||
build:
|
|
||||||
context: ../backend
|
|
||||||
dockerfile: Musify.StreamingGateway.Dockerfile
|
|
||||||
container_name: musify-gateway
|
|
||||||
depends_on:
|
|
||||||
seaweedfs:
|
|
||||||
condition: service_healthy
|
|
||||||
environment:
|
|
||||||
ASPNETCORE_ENVIRONMENT: Production
|
|
||||||
ASPNETCORE_URLS: http://+:8081
|
|
||||||
StreamTicket__PublicKeyPath: "/keys/stream_public.pem"
|
|
||||||
ReverseProxy__Clusters__seaweed-filer__Destinations__filer__Address: "http://seaweedfs:8888/"
|
|
||||||
volumes:
|
|
||||||
- ./keys:/keys:ro
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
web-player:
|
|
||||||
build:
|
|
||||||
context: ../web-player
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
container_name: musify-web-player
|
|
||||||
depends_on:
|
|
||||||
- api
|
|
||||||
environment:
|
|
||||||
NODE_ENV: production
|
|
||||||
ORIGIN: "https://${WEB_DOMAIN}"
|
|
||||||
ZITADEL_ISSUER: "https://${AUTH_DOMAIN}"
|
|
||||||
ZITADEL_CLIENT_ID: "${WEB_CLIENT_ID}"
|
|
||||||
ZITADEL_CLIENT_SECRET: "${WEB_CLIENT_SECRET}"
|
|
||||||
AUTH_REDIRECT_URI: "https://${WEB_DOMAIN}/auth/callback"
|
|
||||||
AUTH_POST_LOGOUT_URI: "https://${WEB_DOMAIN}/"
|
|
||||||
SESSION_SECRET: "${SESSION_SECRET}"
|
|
||||||
API_BASE_URL: "http://api:5111"
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
nginx:
|
|
||||||
image: nginx:1.27-alpine
|
|
||||||
container_name: musify-nginx
|
|
||||||
depends_on:
|
|
||||||
- api
|
|
||||||
- gateway
|
|
||||||
- zitadel
|
|
||||||
- web-player
|
|
||||||
ports:
|
|
||||||
- "80:80"
|
|
||||||
- "443:443"
|
|
||||||
volumes:
|
|
||||||
- ./nginx/conf.d:/etc/nginx/conf.d:ro
|
|
||||||
- ./nginx/certs:/etc/nginx/certs:ro
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
postgres_data:
|
|
||||||
rabbitmq_data:
|
|
||||||
seaweedfs_data:
|
|
||||||
@@ -1,163 +0,0 @@
|
|||||||
name: musify-dev
|
|
||||||
|
|
||||||
# Infrastructure stack for Musify development: everything the backend needs
|
|
||||||
# (Postgres, Zitadel, RabbitMQ, SeaweedFS + bucket, Jaeger/OTLP). The .NET apps
|
|
||||||
# run from the IDE and connect over the published ports. Config in deploy/.env
|
|
||||||
#
|
|
||||||
# docker compose -f deploy/docker-compose.yml up -d
|
|
||||||
|
|
||||||
services:
|
|
||||||
postgres:
|
|
||||||
image: postgres:17-alpine
|
|
||||||
container_name: musify-postgres
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: ${POSTGRES_USER}
|
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
|
||||||
POSTGRES_DB: ${POSTGRES_DB}
|
|
||||||
ports:
|
|
||||||
- "${POSTGRES_HOST_PORT}:5432"
|
|
||||||
volumes:
|
|
||||||
- postgres_data:/var/lib/postgresql/data
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 10
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
pgadmin:
|
|
||||||
image: dpage/pgadmin4:latest
|
|
||||||
container_name: musify-pgadmin
|
|
||||||
environment:
|
|
||||||
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL}
|
|
||||||
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD}
|
|
||||||
PGADMIN_CONFIG_SERVER_MODE: "False"
|
|
||||||
depends_on:
|
|
||||||
postgres:
|
|
||||||
condition: service_healthy
|
|
||||||
ports:
|
|
||||||
- "${PGADMIN_HOST_PORT}:80"
|
|
||||||
volumes:
|
|
||||||
- pgadmin_data:/var/lib/pgadmin
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
zitadel:
|
|
||||||
image: ghcr.io/zitadel/zitadel:latest
|
|
||||||
container_name: musify-zitadel
|
|
||||||
command: 'start-from-init --masterkeyFromEnv --tlsMode disabled'
|
|
||||||
environment:
|
|
||||||
ZITADEL_MASTERKEY: ${ZITADEL_MASTERKEY}
|
|
||||||
ZITADEL_EXTERNALSECURE: "false"
|
|
||||||
ZITADEL_EXTERNALDOMAIN: ${ZITADEL_EXTERNAL_DOMAIN}
|
|
||||||
ZITADEL_EXTERNALPORT: ${ZITADEL_EXTERNAL_PORT}
|
|
||||||
ZITADEL_TLS_ENABLED: "false"
|
|
||||||
# Reuses the same Postgres: the admin user creates the 'zitadel' database
|
|
||||||
# and the 'zitadel' user on first start.
|
|
||||||
ZITADEL_DATABASE_POSTGRES_HOST: postgres
|
|
||||||
ZITADEL_DATABASE_POSTGRES_PORT: 5432
|
|
||||||
ZITADEL_DATABASE_POSTGRES_DATABASE: zitadel
|
|
||||||
ZITADEL_DATABASE_POSTGRES_USER_USERNAME: zitadel
|
|
||||||
ZITADEL_DATABASE_POSTGRES_USER_PASSWORD: zitadel
|
|
||||||
ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE: disable
|
|
||||||
ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME: ${POSTGRES_USER}
|
|
||||||
ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD: ${POSTGRES_PASSWORD}
|
|
||||||
ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE: disable
|
|
||||||
# Initial console admin. Login: <user>@zitadel.<domain>
|
|
||||||
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_USERNAME: ${ZITADEL_ADMIN_USERNAME}
|
|
||||||
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORD: ${ZITADEL_ADMIN_PASSWORD}
|
|
||||||
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORDCHANGEREQUIRED: "false"
|
|
||||||
# Service account + PAT for automatic provisioning. Zitadel writes the PAT
|
|
||||||
# to PATPATH on init; provision.ps1 uses it to create the project and app.
|
|
||||||
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_USERNAME: zitadel-admin-sa
|
|
||||||
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_NAME: Admin Service Account
|
|
||||||
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_PAT_EXPIRATIONDATE: "2099-01-01T00:00:00Z"
|
|
||||||
ZITADEL_FIRSTINSTANCE_PATPATH: /output/admin-sa.pat
|
|
||||||
depends_on:
|
|
||||||
postgres:
|
|
||||||
condition: service_healthy
|
|
||||||
ports:
|
|
||||||
- "${ZITADEL_EXTERNAL_PORT}:8080"
|
|
||||||
volumes:
|
|
||||||
- ./zitadel/.output:/output
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
rabbitmq:
|
|
||||||
image: rabbitmq:4-management-alpine
|
|
||||||
container_name: musify-rabbitmq
|
|
||||||
environment:
|
|
||||||
RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER}
|
|
||||||
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS}
|
|
||||||
ports:
|
|
||||||
- "5672:5672" # AMQP -> MassTransit (API + Worker)
|
|
||||||
- "15672:15672" # management UI
|
|
||||||
volumes:
|
|
||||||
- rabbitmq_data:/var/lib/rabbitmq
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 10
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
seaweedfs:
|
|
||||||
image: chrislusf/seaweedfs:latest
|
|
||||||
container_name: musify-seaweedfs
|
|
||||||
# All-in-one dev server: master + volume + filer + S3 gateway.
|
|
||||||
command: >
|
|
||||||
server
|
|
||||||
-ip.bind=0.0.0.0
|
|
||||||
-dir=/data
|
|
||||||
-master.volumeSizeLimitMB=1024
|
|
||||||
-volume.max=0
|
|
||||||
-filer
|
|
||||||
-s3
|
|
||||||
-s3.config=/etc/seaweedfs/s3.conf
|
|
||||||
ports:
|
|
||||||
- "9333:9333" # master (admin/UI)
|
|
||||||
- "8888:8888" # filer HTTP -> StreamingGateway
|
|
||||||
- "8333:8333" # S3 API -> WebApi/Worker (InfrastructureStorage:Address)
|
|
||||||
volumes:
|
|
||||||
- seaweedfs_data:/data
|
|
||||||
- ./seaweedfs/s3.conf:/etc/seaweedfs/s3.conf:ro
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "wget", "-q", "-O", "-", "http://localhost:9333/cluster/healthz"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 10
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
# One-shot: creates the app bucket in SeaweedFS (it is not auto-created).
|
|
||||||
seaweedfs-init:
|
|
||||||
image: amazon/aws-cli:latest
|
|
||||||
container_name: musify-seaweedfs-init
|
|
||||||
depends_on:
|
|
||||||
seaweedfs:
|
|
||||||
condition: service_healthy
|
|
||||||
environment:
|
|
||||||
AWS_ACCESS_KEY_ID: ${S3_ACCESS_KEY}
|
|
||||||
AWS_SECRET_ACCESS_KEY: ${S3_SECRET_KEY}
|
|
||||||
AWS_DEFAULT_REGION: us-east-1
|
|
||||||
entrypoint: ["/bin/sh", "-c"]
|
|
||||||
command:
|
|
||||||
- |
|
|
||||||
aws --endpoint-url http://seaweedfs:8333 s3 mb s3://${S3_BUCKET} 2>/dev/null || true
|
|
||||||
aws --endpoint-url http://seaweedfs:8333 s3 ls
|
|
||||||
restart: "no"
|
|
||||||
|
|
||||||
# OTLP collector + trace UI (MassTransit/EF/HTTP spans). OtlpEndpoint = :4317
|
|
||||||
jaeger:
|
|
||||||
image: jaegertracing/all-in-one:latest
|
|
||||||
container_name: musify-jaeger
|
|
||||||
environment:
|
|
||||||
COLLECTOR_OTLP_ENABLED: "true"
|
|
||||||
ports:
|
|
||||||
- "16686:16686" # UI
|
|
||||||
- "4317:4317" # OTLP gRPC
|
|
||||||
- "4318:4318" # OTLP HTTP
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
postgres_data:
|
|
||||||
pgadmin_data:
|
|
||||||
rabbitmq_data:
|
|
||||||
seaweedfs_data:
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"identities": [
|
|
||||||
{
|
|
||||||
"name": "admin_user",
|
|
||||||
"credentials": [
|
|
||||||
{
|
|
||||||
"accessKey": "admin_access_key",
|
|
||||||
"secretKey": "admin_secret_key"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"actions": ["Admin", "Read", "Write"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "read_only_user",
|
|
||||||
"credentials": [
|
|
||||||
{
|
|
||||||
"accessKey": "readonly_access_key",
|
|
||||||
"secretKey": "readonly_secret_key"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"actions": ["Read"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
# Secrets Zitadel writes on init (do not version)
|
|
||||||
.output/
|
|
||||||
Reference in New Issue
Block a user