From f1fe15d1b560d43db654c27238b18461e5d0f870 Mon Sep 17 00:00:00 2001 From: Guillem Hernandez Sola Date: Tue, 30 Jun 2026 13:12:37 +0200 Subject: [PATCH] Tots els compose --- adguard/docker-compose.yml | 34 +++ astro/Dockerfile | 17 ++ astro/docker-compose.yml | 28 +++ astro/nginx.conf | 16 ++ authentik/docker-compose.yml | 103 ++++++++ bentopdf/docker-compose.yml | 19 ++ beszel/docker-compose.yml | 23 ++ ddns-go/docker-compose.yml | 30 +++ flowise/docker-compose.yml | 66 +++++ gitea/docker-compose.yml | 83 +++++++ glance/docker-compose.yml | 21 ++ glance/glance.yml | 10 + headscale/config.yml | 15 ++ headscale/docker-compose.yml | 48 ++++ homarr/docker-compose.yml | 28 +++ immich/docker-compose.yml | 112 +++++++++ jenkins/docker-compose.yml | 44 ++++ kavita/docker-compose.yml | 27 +++ localai/docker-compose.yml | 42 ++++ mariadb/docker-compose.yml | 59 +++++ mautic/docker-compose.yml | 100 ++++++++ mermaid/docker-compose.yml | 24 ++ moodle/docker-compose.yml | 62 +++++ n8n/docker-compose.yml | 64 +++++ nextcloud/docker-compose.yml | 110 +++++++++ ollama/docker-compose.yml | 46 ++++ onlyoffice/docker-compose.yml | 40 +++ pihole/docker-compose.yml | 36 +++ plex/docker-compose.yml | 431 +++++++++++++++++++++++++++++++++ portainer/docker-compose.yml | 24 ++ postgres/docker-compose.yml | 64 +++++ redis/docker-compose.yml | 70 ++++++ vaultwarden/docker-compose.yml | 49 ++++ wg-easy/docker-compose.yml | 38 +++ wordpress/docker-compose.yml | 92 +++++++ 35 files changed, 2075 insertions(+) create mode 100644 adguard/docker-compose.yml create mode 100644 astro/Dockerfile create mode 100644 astro/docker-compose.yml create mode 100644 astro/nginx.conf create mode 100644 authentik/docker-compose.yml create mode 100644 bentopdf/docker-compose.yml create mode 100644 beszel/docker-compose.yml create mode 100644 ddns-go/docker-compose.yml create mode 100644 flowise/docker-compose.yml create mode 100644 gitea/docker-compose.yml create mode 100644 glance/docker-compose.yml create mode 100644 glance/glance.yml create mode 100644 headscale/config.yml create mode 100644 headscale/docker-compose.yml create mode 100644 homarr/docker-compose.yml create mode 100644 immich/docker-compose.yml create mode 100644 jenkins/docker-compose.yml create mode 100644 kavita/docker-compose.yml create mode 100644 localai/docker-compose.yml create mode 100644 mariadb/docker-compose.yml create mode 100644 mautic/docker-compose.yml create mode 100644 mermaid/docker-compose.yml create mode 100644 moodle/docker-compose.yml create mode 100644 n8n/docker-compose.yml create mode 100644 nextcloud/docker-compose.yml create mode 100644 ollama/docker-compose.yml create mode 100644 onlyoffice/docker-compose.yml create mode 100644 pihole/docker-compose.yml create mode 100644 plex/docker-compose.yml create mode 100644 portainer/docker-compose.yml create mode 100644 postgres/docker-compose.yml create mode 100644 redis/docker-compose.yml create mode 100644 vaultwarden/docker-compose.yml create mode 100644 wg-easy/docker-compose.yml create mode 100644 wordpress/docker-compose.yml diff --git a/adguard/docker-compose.yml b/adguard/docker-compose.yml new file mode 100644 index 0000000..049e683 --- /dev/null +++ b/adguard/docker-compose.yml @@ -0,0 +1,34 @@ +# /opt/stacks/adguardhome/docker-compose.yml + +services: + adguardhome: + container_name: adguardhome + image: adguard/adguardhome:latest + restart: unless-stopped + ports: + - "53:53/tcp" # DNS TCP — públic (necessari per a tota la xarxa) + - "53:53/udp" # DNS UDP — públic (necessari per a tota la xarxa) + - "853:853/tcp" # DNS-over-TLS — públic (clients externs) + - "3000:3000" # Wizard inicial (només local — tancar després de setup) + - "8080:80" # Dashboard web (només local → via Nginx) + volumes: + - adguard_work:/opt/adguardhome/work # Volum named — evita reset al wizard + - adguard_conf:/opt/adguardhome/conf # Volum named — configuració persistent + environment: + TZ: Europe/Madrid + cap_add: + - NET_ADMIN + networks: + - dns-net + - shared_net + +volumes: + adguard_work: + adguard_conf: + +networks: + dns-net: + driver: bridge + shared_net: + external: true + name: shared_net diff --git a/astro/Dockerfile b/astro/Dockerfile new file mode 100644 index 0000000..015d833 --- /dev/null +++ b/astro/Dockerfile @@ -0,0 +1,17 @@ +# ── Fase 1: Compilació amb Node.js ──────── +FROM node:20-alpine AS builder +WORKDIR /app +# Instal·la dependències primer (aprofita la cache de Docker) +COPY package*.json ./ +RUN npm ci +# Copia el codi font i compila +COPY . . +RUN npm run build + +# ── Fase 2: Servir amb Nginx (imatge mínima) ── +FROM nginx:alpine +# Copia NOMÉS la carpeta dist/ de la fase anterior +COPY --from=builder /app/dist /usr/share/nginx/html +# Configuració Nginx per a SPA i rutes d'Astro +COPY nginx.conf /etc/nginx/conf.d/default.conf +EXPOSE 80 \ No newline at end of file diff --git a/astro/docker-compose.yml b/astro/docker-compose.yml new file mode 100644 index 0000000..7b70d84 --- /dev/null +++ b/astro/docker-compose.yml @@ -0,0 +1,28 @@ +# /opt/stacks/astro-web/docker-compose.yml + +services: + + astro-web: + container_name: astro-web + build: + context: . + dockerfile: Dockerfile + image: astro-web:local # Nom explícit per a la imatge buildada + restart: unless-stopped + environment: + TZ: Europe/Madrid + ports: + - "8087:80" # Només local → via Nginx Proxy Manager + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:80"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 15s + networks: + - shared_net + +networks: + shared_net: + external: true + name: shared_net \ No newline at end of file diff --git a/astro/nginx.conf b/astro/nginx.conf new file mode 100644 index 0000000..1aa947d --- /dev/null +++ b/astro/nginx.conf @@ -0,0 +1,16 @@ +server { + listen 80; + root /usr/share/nginx/html; + index index.html; + + # Rutes d'Astro: si no troba el fitxer, retorna 404.html + location / { + try_files $uri $uri/ $uri.html =404; + } + + # Cache agressiva per a assets compilats (hash al nom) + location /_astro/ { + expires 1y; + add_header Cache-Control "public, immutable"; + } +} \ No newline at end of file diff --git a/authentik/docker-compose.yml b/authentik/docker-compose.yml new file mode 100644 index 0000000..ce0babc --- /dev/null +++ b/authentik/docker-compose.yml @@ -0,0 +1,103 @@ +# /opt/stacks/authentik/docker-compose.yml + +services: + + # ── Base de dades PostgreSQL ───────────── + postgresql: + container_name: authentik-postgres + image: postgres:16-alpine + restart: unless-stopped + environment: + POSTGRES_PASSWORD: '${PG_PASS}' + POSTGRES_USER: authentik + POSTGRES_DB: authentik + volumes: + - pg_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U authentik -d authentik"] + interval: 10s + timeout: 5s # Afegit + retries: 5 + start_period: 30s # Afegit + networks: [authentik_net] + + # ── Redis per a sessions ───────────────── + redis: + container_name: authentik-redis + image: redis:7.4-alpine + restart: unless-stopped + command: redis-server --requirepass '${REDIS_PASS}' + healthcheck: + test: ["CMD", "redis-cli", "-a", "${REDIS_PASS}", "ping"] + interval: 10s + timeout: 5s # Afegit + retries: 5 + start_period: 15s # Afegit + networks: [authentik_net] + + # ── Authentik Server (API + UI) ─────────── + server: + container_name: authentik-server + image: ghcr.io/goauthentik/server:2026.5.3 # Actualitzat a darrera versió estable + restart: unless-stopped + command: server + environment: + # ⚠️ NO afegir TZ aquí — trenca OAuth/SAML (doc oficial) + AUTHENTIK_REDIS__HOST: redis + AUTHENTIK_REDIS__PASSWORD: '${REDIS_PASS}' + AUTHENTIK_POSTGRESQL__HOST: postgresql + AUTHENTIK_POSTGRESQL__USER: authentik + AUTHENTIK_POSTGRESQL__NAME: authentik + AUTHENTIK_POSTGRESQL__PASSWORD: '${PG_PASS}' + AUTHENTIK_SECRET_KEY: '${AUTHENTIK_SECRET_KEY}' + AUTHENTIK_ERROR_REPORTING__ENABLED: "false" # String, no booleà YAML + ports: + - "127.0.0.1:9000:9000" # HTTP (només local → via Nginx) + - "127.0.0.1:9443:9443" # HTTPS (només local → via Nginx) + volumes: + - authentik_media:/media + - authentik_templates:/templates + depends_on: + postgresql: + condition: service_healthy + redis: + condition: service_healthy + networks: [authentik_net, shared_net] + + # ── Authentik Worker (tasques en segon pla) ── + worker: + container_name: authentik-worker + image: ghcr.io/goauthentik/server:2026.5.3 # Mateixa versió que server + restart: unless-stopped + command: worker + environment: + # ⚠️ NO afegir TZ aquí — trenca OAuth/SAML (doc oficial) + AUTHENTIK_REDIS__HOST: redis + AUTHENTIK_REDIS__PASSWORD: '${REDIS_PASS}' + AUTHENTIK_POSTGRESQL__HOST: postgresql + AUTHENTIK_POSTGRESQL__USER: authentik + AUTHENTIK_POSTGRESQL__NAME: authentik + AUTHENTIK_POSTGRESQL__PASSWORD: '${PG_PASS}' + AUTHENTIK_SECRET_KEY: '${AUTHENTIK_SECRET_KEY}' + volumes: + - authentik_media:/media + - authentik_templates:/templates # Afegit: worker també necessita templates + - /var/run/docker.sock:/var/run/docker.sock:ro # Gestió automàtica d'Outposts + depends_on: + postgresql: + condition: service_healthy + redis: + condition: service_healthy + networks: [authentik_net] + +volumes: + pg_data: + authentik_media: + authentik_templates: + +networks: + authentik_net: + driver: bridge + shared_net: + external: true + name: shared_net \ No newline at end of file diff --git a/bentopdf/docker-compose.yml b/bentopdf/docker-compose.yml new file mode 100644 index 0000000..1bfe02d --- /dev/null +++ b/bentopdf/docker-compose.yml @@ -0,0 +1,19 @@ +# /opt/stacks/bentopdf/docker-compose.yml + +services: + + bentopdf: + container_name: bentopdf + image: bentopdfteam/bentopdf:latest # Imatge activa — bentopdf/bentopdf és deprecated! + restart: unless-stopped + environment: + TZ: Europe/Madrid + ports: + - "9999:8080" # Només local → via Nginx Proxy Manager + networks: + - shared_net + +networks: + shared_net: + external: true + name: shared_net \ No newline at end of file diff --git a/beszel/docker-compose.yml b/beszel/docker-compose.yml new file mode 100644 index 0000000..8787706 --- /dev/null +++ b/beszel/docker-compose.yml @@ -0,0 +1,23 @@ +# /opt/stacks/beszel/docker-compose.yml + +services: + beszel: + image: henrygd/beszel:latest + container_name: beszel + restart: unless-stopped + ports: + - "8090:8090" # Interfície web (només local) + volumes: + - beszel_data:/beszel_data # Volum named — més estable que path relatiu + environment: + TZ: Europe/Madrid + networks: + - shared_net + +volumes: + beszel_data: + +networks: + shared_net: + external: true + name: shared_net diff --git a/ddns-go/docker-compose.yml b/ddns-go/docker-compose.yml new file mode 100644 index 0000000..a98c43a --- /dev/null +++ b/ddns-go/docker-compose.yml @@ -0,0 +1,30 @@ +# /opt/stacks/ddns/docker-compose.yml + +services: + + ddns-go: + container_name: ddns-go + image: jeessy/ddns-go:latest + restart: unless-stopped + ports: + - "9876:9876" # UI web (només local → via Nginx) + environment: + TZ: Europe/Madrid + volumes: + - ddns_data:/root # Persisteix la configuració + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:9876"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 15s + networks: + - shared_net + +volumes: + ddns_data: + +networks: + shared_net: + external: true + name: shared_net diff --git a/flowise/docker-compose.yml b/flowise/docker-compose.yml new file mode 100644 index 0000000..c09d0a4 --- /dev/null +++ b/flowise/docker-compose.yml @@ -0,0 +1,66 @@ +# /opt/stacks/flowise/docker-compose.yml + +services: + + postgres: + image: postgres:15-alpine + restart: unless-stopped + environment: + POSTGRES_USER: '${POSTGRES_USER}' + POSTGRES_PASSWORD: '${POSTGRES_PASSWORD}' + POSTGRES_DB: flowise + TZ: Europe/Madrid + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d flowise"] + interval: 10s + timeout: 5s # Afegit: timeout explícit + retries: 5 + start_period: 30s # Afegit: marge per a l'arrencada inicial + networks: + - flowise-net + + flowise: + image: flowiseai/flowise:latest + restart: unless-stopped + ports: + - "3000:3000" # Interfície web (només local → via Nginx) + environment: + TZ: Europe/Madrid + # Base de dades + DATABASE_TYPE: postgres + DATABASE_HOST: postgres + DATABASE_PORT: 5432 + DATABASE_NAME: flowise + DATABASE_USER: '${POSTGRES_USER}' + DATABASE_PASSWORD: '${POSTGRES_PASSWORD}' + # Autenticació + FLOWISE_USERNAME: '${FLOWISE_USERNAME}' + FLOWISE_PASSWORD: '${FLOWISE_PASSWORD}' + # Seguretat — CRÍTIC: fes backup d'aquesta clau! + FLOWISE_SECRETKEY_OVERWRITE: '${FLOWISE_SECRETKEY}' + # Paths i logs + APIKEY_PATH: /root/.flowise + SECRETKEY_PATH: /root/.flowise + LOG_LEVEL: info + LOG_PATH: /root/.flowise/logs + volumes: + - flowise_data:/root/.flowise + depends_on: + postgres: + condition: service_healthy + networks: + - flowise-net + - shared_net + +volumes: + postgres_data: + flowise_data: + +networks: + flowise-net: + driver: bridge + shared_net: + external: true + name: shared_net \ No newline at end of file diff --git a/gitea/docker-compose.yml b/gitea/docker-compose.yml new file mode 100644 index 0000000..6054d8b --- /dev/null +++ b/gitea/docker-compose.yml @@ -0,0 +1,83 @@ +# /opt/stacks/gitea/docker-compose.yml + +services: + + # ── MariaDB ────────────────────────────── + db: + container_name: gitea-db + image: mariadb:10.11 + restart: unless-stopped + environment: + TZ: Europe/Madrid + MYSQL_ROOT_PASSWORD: '${MYSQL_ROOT_PASSWORD}' + MYSQL_DATABASE: gitea + MYSQL_USER: gitea + MYSQL_PASSWORD: '${MYSQL_PASSWORD}' + volumes: + - gitea_db:/var/lib/mysql + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + networks: [gitea_net] + + # ── Gitea ──────────────────────────────── + gitea: + container_name: gitea + image: gitea/gitea:1 # Pinnat a major version — evita trencaments per latest + restart: unless-stopped + environment: + TZ: Europe/Madrid # Substitueix els bind mounts de timezone + USER_UID: 1000 + USER_GID: 1000 + # Connexió a MariaDB + GITEA__database__DB_TYPE: mysql + GITEA__database__HOST: db:3306 + GITEA__database__NAME: gitea + GITEA__database__USER: gitea + GITEA__database__PASSWD: '${MYSQL_PASSWORD}' + # URL pública (obligatori per a clons SSH i web) + GITEA__server__ROOT_URL: 'https://git.domini.com/' + GITEA__server__SSH_DOMAIN: git.domini.com + GITEA__server__SSH_PORT: 2222 + GITEA__server__DOMAIN: git.domini.com + # Desactiva el registre públic + GITEA__service__DISABLE_REGISTRATION: 'true' + # Seguretat + GITEA__security__SECRET_KEY: '${GITEA_SECRET_KEY}' + GITEA__security__INTERNAL_TOKEN: '${GITEA_INTERNAL_TOKEN}' + # Mailer — descomenta i configura per a notificacions per email + # GITEA__mailer__ENABLED: 'true' + # GITEA__mailer__SMTP_ADDR: mail.domini.com + # GITEA__mailer__SMTP_PORT: '587' + # GITEA__mailer__USER: '${GITEA_SMTP_USER}' + # GITEA__mailer__PASSWD: '${GITEA_SMTP_PASSWORD}' + # GITEA__mailer__FROM: 'Gitea ' + volumes: + - gitea_data:/data + ports: + - "3000:3000" # Web UI → via Nginx Proxy Manager + - "2222:22" # SSH públic — necessari per a git push/pull directe + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 60s + depends_on: + db: + condition: service_healthy + networks: [gitea_net, shared_net] + +volumes: + gitea_db: + gitea_data: + +networks: + gitea_net: + driver: bridge + shared_net: + external: true + name: shared_net \ No newline at end of file diff --git a/glance/docker-compose.yml b/glance/docker-compose.yml new file mode 100644 index 0000000..904bf57 --- /dev/null +++ b/glance/docker-compose.yml @@ -0,0 +1,21 @@ +# /opt/stacks/glance/docker-compose.yml + +services: + glance: + image: glanceapp/glance:latest + container_name: glance + restart: unless-stopped + ports: + - "127.0.0.1:8080:8080" # Interfície web (només local) + volumes: + - ./glance.yml:/app/glance.yml:ro # Configuració principal + - ./assets:/app/assets:ro # Assets locals (icones, imatges) + environment: + TZ: Europe/Madrid + networks: + - shared_net + +networks: + shared_net: + external: true + name: shared_net \ No newline at end of file diff --git a/glance/glance.yml b/glance/glance.yml new file mode 100644 index 0000000..d189efd --- /dev/null +++ b/glance/glance.yml @@ -0,0 +1,10 @@ +server: + port: 8080 + +pages: + - name: Inici + columns: + - size: full + widgets: + - type: clock + hour-format: 24h \ No newline at end of file diff --git a/headscale/config.yml b/headscale/config.yml new file mode 100644 index 0000000..08e4206 --- /dev/null +++ b/headscale/config.yml @@ -0,0 +1,15 @@ +server_url: https://headscale.el-teu-domini.com +listen_addr: 0.0.0.0:8080 +metrics_listen_addr: 0.0.0.0:9090 +private_key_path: /var/lib/headscale/private.key +noise: + private_key_path: /var/lib/headscale/noise_private.key +ip_prefixes: + - 100.64.0.0/10 # Rang IPs Tailscale +dns_config: + magic_dns: true + base_domain: homelab.intern + nameservers: + - 9.9.9.9 +db_type: sqlite3 +db_path: /var/lib/headscale/db.sqlite \ No newline at end of file diff --git a/headscale/docker-compose.yml b/headscale/docker-compose.yml new file mode 100644 index 0000000..1b87865 --- /dev/null +++ b/headscale/docker-compose.yml @@ -0,0 +1,48 @@ +# /opt/stacks/headscale/docker-compose.yml + +services: + headscale: + container_name: headscale + image: headscale/headscale:latest + restart: unless-stopped + command: serve + ports: + - "8080:8080" # API i clients (només local → via Nginx) + - "9090:9090" # Mètriques Prometheus (mai públic) + volumes: + - headscale_config:/etc/headscale # Configuració persistent + - headscale_data:/var/lib/headscale # Dades persistents + - headscale_socket:/var/run/headscale # Socket Unix per a la UI + environment: + TZ: Europe/Madrid + networks: + - headscale-net + - shared_net + + headscale-ui: + container_name: headscale-ui + image: ghcr.io/gurucomputing/headscale-ui:latest + restart: unless-stopped + ports: + - "8081:80" # Interfície web (només local → via Nginx) + volumes: + - headscale_socket:/var/run/headscale # Socket Unix compartit amb headscale + environment: + TZ: Europe/Madrid + depends_on: + - headscale + networks: + - headscale-net + - shared_net + +volumes: + headscale_config: + headscale_data: + headscale_socket: + +networks: + headscale-net: + driver: bridge + shared_net: + external: true + name: shared_net \ No newline at end of file diff --git a/homarr/docker-compose.yml b/homarr/docker-compose.yml new file mode 100644 index 0000000..4e937a5 --- /dev/null +++ b/homarr/docker-compose.yml @@ -0,0 +1,28 @@ +# /opt/stacks/homarr/docker-compose.yml + +services: + homarr: + image: ghcr.io/ajnart/homarr:latest + container_name: homarr + restart: unless-stopped + ports: + - "7575:7575" # Interfície web (només local) + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro # :ro = només lectura + - homarr_configs:/app/data/configs # Configuració + - homarr_icons:/app/public/icons # Icones personalitzades + - homarr_data:/data # Dades persistents + environment: + TZ: Europe/Madrid + networks: + - shared_net + +volumes: + homarr_configs: + homarr_icons: + homarr_data: + +networks: + shared_net: + external: true + name: shared_net \ No newline at end of file diff --git a/immich/docker-compose.yml b/immich/docker-compose.yml new file mode 100644 index 0000000..3f28cf2 --- /dev/null +++ b/immich/docker-compose.yml @@ -0,0 +1,112 @@ +# /opt/stacks/immich/docker-compose.yml +# Documentació oficial: https://docs.immich.app/install/docker-compose/ + +services: + + # ── Immich Server ───────────────────────── + immich-server: + container_name: immich-server + image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release} + restart: unless-stopped + environment: + TZ: ${TZ} + DB_URL: "postgresql://${DB_USERNAME}:${DB_PASSWORD}@immich-db:5432/${DB_DATABASE_NAME}" + REDIS_HOSTNAME: immich-redis + IMMICH_ENV: production + UPLOAD_LOCATION: ${UPLOAD_LOCATION} + volumes: + - ${UPLOAD_LOCATION}:/usr/src/app/upload + - /etc/localtime:/etc/localtime:ro + ports: + - "127.0.0.1:2283:2283" # Web UI → via Nginx Proxy Manager + depends_on: + immich-db: + condition: service_healthy + immich-redis: + condition: service_healthy + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:2283/api/server/ping"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 60s + networks: + - immich_net + - shared_net + + + # ── Machine Learning ────────────────────── + immich-machine-learning: + container_name: immich-machine-learning + image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release} + restart: unless-stopped + environment: + TZ: ${TZ} + IMMICH_ENV: production + volumes: + - model-cache:/cache + healthcheck: + test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:3003/ping')"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 120s # El model triga a carregar-se + networks: + - immich_net + + + # ── Redis ───────────────────────────────── + immich-redis: + container_name: immich-redis + image: redis:7-alpine + restart: unless-stopped + environment: + TZ: ${TZ} + volumes: + - redis-data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s + networks: + - immich_net + + + # ── PostgreSQL + VectorChord ─────────────── + # Imatge oficial d'Immich — substitueix tensorchord/pgvecto-rs (obsoleta) + immich-db: + container_name: immich-db + image: ghcr.io/immich-app/postgres:17-vectorchord + restart: unless-stopped + environment: + TZ: ${TZ} + POSTGRES_DB: ${DB_DATABASE_NAME} + POSTGRES_USER: ${DB_USERNAME} + POSTGRES_PASSWORD: ${DB_PASSWORD} + POSTGRES_INITDB_ARGS: "--data-checksums" + volumes: + - pgdata:/var/lib/postgresql/data # Volum named — mai network share + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME} -d ${DB_DATABASE_NAME}"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + networks: + - immich_net + + +volumes: + pgdata: + model-cache: + redis-data: + + +networks: + immich_net: + driver: bridge + shared_net: + external: true + name: shared_net \ No newline at end of file diff --git a/jenkins/docker-compose.yml b/jenkins/docker-compose.yml new file mode 100644 index 0000000..616f568 --- /dev/null +++ b/jenkins/docker-compose.yml @@ -0,0 +1,44 @@ +# /opt/stacks/jenkins/docker-compose.yml +# +# SEGURETAT: Jenkins s'executa com a usuari jenkins (uid 1000), no com a root. +# Per accedir a Docker des de pipelines, s'usa Docker-in-Docker (dind) o +# s'afegeix l'usuari jenkins al grup docker del host (vegeu notes al final). + +services: + + jenkins: + container_name: jenkins + image: jenkins/jenkins:lts-jdk17 + restart: unless-stopped + # NO usar user: root — risc crític amb docker.sock muntat + # L'uid 1000 és l'usuari jenkins per defecte de la imatge oficial + environment: + TZ: Europe/Madrid + JAVA_OPTS: >- + -Djenkins.install.runSetupWizard=true + -Xmx512m + -Xms256m + volumes: + - jenkins_home:/var/jenkins_home + # Docker socket — NOMÉS si has fet: sudo usermod -aG docker + # i has ajustat els permisos del socket (vegeu notes) + # - /var/run/docker.sock:/var/run/docker.sock + ports: + - "8089:8080" # Web UI → via Nginx Proxy Manager + - "50000:50000" # Agents Jenkins (JNLP) + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/login"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 90s # Jenkins triga a arrencar + networks: + - shared_net + +volumes: + jenkins_home: + +networks: + shared_net: + external: true + name: shared_net \ No newline at end of file diff --git a/kavita/docker-compose.yml b/kavita/docker-compose.yml new file mode 100644 index 0000000..fb9d0ac --- /dev/null +++ b/kavita/docker-compose.yml @@ -0,0 +1,27 @@ +# /opt/stacks/kavita/docker-compose.yml + +services: + + kavita: + container_name: kavita + image: jvmilazz0/kavita:latest + restart: unless-stopped + environment: + TZ: Europe/Madrid + volumes: + # Carpeta amb els teus llibres/còmics/manga (read-only — Kavita no necessita escriure-hi) + - /ruta/als/teus/llibres:/manga:ro + # Configuració i base de dades SQLite de Kavita + - kavita_config:/kavita/config + ports: + - "127.0.0.1:5000:5000" # Només local → via Nginx Proxy Manager + networks: + - shared_net + +volumes: + kavita_config: + +networks: + shared_net: + external: true + name: shared_net \ No newline at end of file diff --git a/localai/docker-compose.yml b/localai/docker-compose.yml new file mode 100644 index 0000000..2905f13 --- /dev/null +++ b/localai/docker-compose.yml @@ -0,0 +1,42 @@ +# /opt/stacks/localai/docker-compose.yml + +services: + + localai: + container_name: localai + image: localai/localai:latest-aio-cpu + restart: unless-stopped + ports: + - "8080:8080" # API LocalAI (només local → via Nginx) + environment: + TZ: Europe/Madrid + # Configuració de rendiment (prefix LOCALAI_ obligatori) + LOCALAI_THREADS: 4 # Fils de CPU a usar + LOCALAI_CONTEXT_SIZE: 4096 # Context màxim en tokens + LOCALAI_MODELS_PATH: /models # Path dels models GGUF + # Logs i telemetria + LOCALAI_LOG_LEVEL: info + LOCALAI_DISABLE_USAGE_STATS: true + # Opcionals recomanats + LOCALAI_PARALLEL_REQUESTS: true # Permet múltiples peticions simultànies + LOCALAI_SINGLE_ACTIVE_BACKEND: true # Allibera memòria quan es canvia de model + volumes: + - localai_models:/models # Models GGUF persistents + - localai_config:/etc/localai # Configuració de models YAML + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/readyz"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 120s # LocalAI triga a arrencar la 1a vegada + networks: + - shared_net + +volumes: + localai_models: + localai_config: + +networks: + shared_net: + external: true + name: shared_net \ No newline at end of file diff --git a/mariadb/docker-compose.yml b/mariadb/docker-compose.yml new file mode 100644 index 0000000..cac856c --- /dev/null +++ b/mariadb/docker-compose.yml @@ -0,0 +1,59 @@ +# /opt/stacks/mariadb/docker-compose.yml + +services: + + # ── Servei MariaDB ─────────────────────── + mariadb: + container_name: mariadb + image: mariadb:11.4 # Pinnat a LTS estable + restart: unless-stopped + environment: + TZ: Europe/Madrid + MARIADB_ROOT_PASSWORD: '${MARIADB_ROOT_PASSWORD}' + MARIADB_DATABASE: '${MARIADB_DATABASE}' + MARIADB_USER: '${MARIADB_USER}' + MARIADB_PASSWORD: '${MARIADB_PASSWORD}' + MARIADB_CHARACTER_SET_SERVER: utf8mb4 + MARIADB_COLLATION_SERVER: utf8mb4_unicode_ci + ports: + - "127.0.0.1:3306:3306" # Accés local per eines externes (DBeaver, etc.) + volumes: + - mariadb_data:/var/lib/mysql + - mariadb_init:/docker-entrypoint-initdb.d # Volum named en lloc de bind mount + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + interval: 10s + timeout: 5s # Afegit: timeout explícit + retries: 5 + start_period: 60s # Afegit: marge per a scripts d'inicialització + networks: + - db_net + + # ── Adminer: UI web per gestionar la BD ── + adminer: + container_name: adminer + image: adminer:latest + restart: unless-stopped + ports: + - "127.0.0.1:8090:8080" # UI web (només local → via Nginx) + environment: + TZ: Europe/Madrid + ADMINER_DEFAULT_SERVER: mariadb # Pre-omple el camp servidor a la UI + ADMINER_DESIGN: pepa-linha # Tema visual més modern (opcional) + depends_on: + mariadb: + condition: service_healthy + networks: + - db_net + - shared_net + +volumes: + mariadb_data: + mariadb_init: # Posa els teus scripts SQL aquí + +networks: + db_net: + driver: bridge + shared_net: + external: true + name: shared_net \ No newline at end of file diff --git a/mautic/docker-compose.yml b/mautic/docker-compose.yml new file mode 100644 index 0000000..66d6e3f --- /dev/null +++ b/mautic/docker-compose.yml @@ -0,0 +1,100 @@ +# /opt/stacks/mautic/docker-compose.yml + +services: + + # ── MariaDB per a Mautic ───────────────── + db: + container_name: mautic-db + image: mariadb:10.11 + restart: unless-stopped + environment: + TZ: Europe/Madrid + MYSQL_ROOT_PASSWORD: '${MYSQL_ROOT_PASSWORD}' + MYSQL_DATABASE: mautic + MYSQL_USER: mautic + MYSQL_PASSWORD: '${MYSQL_PASSWORD}' + volumes: + - mautic_db:/var/lib/mysql + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + networks: [mautic_net] + + # ── Mautic — Aplicació principal (web) ─── + mautic_web: + container_name: mautic-web + image: mautic/mautic:6-apache # Actualitzat a Mautic 6 + restart: unless-stopped + env_file: .mautic_env # Variables específiques de Mautic + environment: + TZ: Europe/Madrid + MAUTIC_ROLE: mautic_web # Mode natiu de la imatge oficial + volumes: + - mautic_config:/var/www/html/config # Configuració local.php + - mautic_logs:/var/www/html/var/logs # Logs de l'aplicació + - mautic_media:/var/www/html/docroot/media # Fitxers pujats i generats + ports: + - "127.0.0.1:8083:80" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost/mtc.js"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 60s + depends_on: + db: + condition: service_healthy + networks: [mautic_net, shared_net] + + # ── Mautic Worker — processa cues async ── + mautic_worker: + container_name: mautic-worker + image: mautic/mautic:6-apache + restart: unless-stopped + env_file: .mautic_env + environment: + TZ: Europe/Madrid + MAUTIC_ROLE: mautic_worker # Mode worker natiu + MAUTIC_MESSENGER_CONSUME_OPTS: '--time-limit=300 --memory-limit=128M' + volumes: + - mautic_config:/var/www/html/config + - mautic_logs:/var/www/html/var/logs + - mautic_media:/var/www/html/docroot/media + depends_on: + mautic_web: + condition: service_healthy + networks: [mautic_net] + + # ── Mautic Cron — tasques programades ──── + mautic_cron: + container_name: mautic-cron + image: mautic/mautic:6-apache + restart: unless-stopped + env_file: .mautic_env + environment: + TZ: Europe/Madrid + MAUTIC_ROLE: mautic_cron # Mode cron natiu — gestiona tots els jobs + volumes: + - mautic_config:/var/www/html/config + - mautic_logs:/var/www/html/var/logs + - mautic_media:/var/www/html/docroot/media + depends_on: + mautic_web: + condition: service_healthy + networks: [mautic_net] + +volumes: + mautic_db: + mautic_config: + mautic_logs: + mautic_media: + +networks: + mautic_net: + driver: bridge + shared_net: + external: true + name: shared_net \ No newline at end of file diff --git a/mermaid/docker-compose.yml b/mermaid/docker-compose.yml new file mode 100644 index 0000000..1a5cd8c --- /dev/null +++ b/mermaid/docker-compose.yml @@ -0,0 +1,24 @@ +# /opt/stacks/mermaid/docker-compose.yml +# +# IMPORTANT: mermaid-live-editor NO suporta variables d'entorn en runtime. +# Les opcions com MERMAID_RENDERER_URL són build arguments — s'ignoren aquí. +# La imatge pre-built usa mermaid.ink com a renderer (requereix internet). +# Per a un entorn offline complet, cal fer un build propi (vegeu README). + +services: + + mermaid: + container_name: mermaid + image: ghcr.io/mermaid-js/mermaid-live-editor:latest + restart: unless-stopped + environment: + TZ: Europe/Madrid + ports: + - "8088:8080" # Només local → via Nginx Proxy Manager + networks: + - shared_net + +networks: + shared_net: + external: true + name: shared_net \ No newline at end of file diff --git a/moodle/docker-compose.yml b/moodle/docker-compose.yml new file mode 100644 index 0000000..813d46c --- /dev/null +++ b/moodle/docker-compose.yml @@ -0,0 +1,62 @@ +# /opt/stacks/moodle/docker-compose.yml + +services: + + # ── MariaDB per a Moodle ───────────────── + db: + container_name: moodle-db + image: mariadb:10.11 + restart: unless-stopped + environment: + TZ: Europe/Madrid + MYSQL_ROOT_PASSWORD: '${MYSQL_ROOT_PASSWORD}' + MYSQL_DATABASE: moodle + MYSQL_USER: moodle + MYSQL_PASSWORD: '${MYSQL_PASSWORD}' + command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + volumes: + - moodle_db:/var/lib/mysql + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + networks: [moodle_net] + + # ── Moodle — Aplicació principal ───────── + moodle: + container_name: moodle + image: moodlehq/moodle-php-apache:8.2-bullseye + restart: unless-stopped + environment: + TZ: Europe/Madrid + volumes: + # Codi de Moodle i config.php + - moodle_html:/var/www/html + # Dades dels usuaris (fitxers pujats, cache, sessions) + - moodle_data:/var/www/moodledata + ports: + - "127.0.0.1:8084:80" # Només local → via Nginx Proxy Manager + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost/login/index.php"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 120s + depends_on: + db: + condition: service_healthy + networks: [moodle_net, shared_net] + +volumes: + moodle_db: + moodle_html: + moodle_data: + +networks: + moodle_net: + driver: bridge + shared_net: + external: true + name: shared_net \ No newline at end of file diff --git a/n8n/docker-compose.yml b/n8n/docker-compose.yml new file mode 100644 index 0000000..0ab6428 --- /dev/null +++ b/n8n/docker-compose.yml @@ -0,0 +1,64 @@ +# /opt/stacks/n8n/docker-compose.yml + +services: + + postgres: + image: postgres:15 + restart: unless-stopped + environment: + POSTGRES_USER: '${POSTGRES_USER}' + POSTGRES_PASSWORD: '${POSTGRES_PASSWORD}' + POSTGRES_DB: '${POSTGRES_DB}' + TZ: Europe/Madrid + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: # Crític: n8n espera que postgres estigui llest + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + networks: + - n8n-net + + n8n: + image: n8nio/n8n:latest + restart: unless-stopped + ports: + - "5678:5678" # Interfície web (només local → via Nginx) + environment: + TZ: Europe/Madrid + GENERIC_TIMEZONE: Europe/Madrid # Necessari per a workflows cron + # Base de dades + DB_TYPE: postgresdb + DB_POSTGRESDB_HOST: postgres + DB_POSTGRESDB_PORT: 5432 + DB_POSTGRESDB_DATABASE: '${POSTGRES_DB}' + DB_POSTGRESDB_USER: '${POSTGRES_USER}' + DB_POSTGRESDB_PASSWORD: '${POSTGRES_PASSWORD}' + # Seguretat + N8N_ENCRYPTION_KEY: '${N8N_ENCRYPTION_KEY}' # CRÍTIC: mai canviar un cop configurat + # URL pública (necessari per a webhooks) + N8N_HOST: '${N8N_HOST}' + N8N_PORT: 5678 + N8N_PROTOCOL: https + WEBHOOK_URL: 'https://${N8N_HOST}/' + volumes: + - n8n_data:/home/node/.n8n + depends_on: + postgres: + condition: service_healthy # Espera que postgres estigui realment llest + networks: + - n8n-net + - shared_net + +volumes: + postgres_data: + n8n_data: + +networks: + n8n-net: + driver: bridge + shared_net: + external: true + name: shared_net \ No newline at end of file diff --git a/nextcloud/docker-compose.yml b/nextcloud/docker-compose.yml new file mode 100644 index 0000000..862a0c1 --- /dev/null +++ b/nextcloud/docker-compose.yml @@ -0,0 +1,110 @@ +# /opt/stacks/nextcloud/docker-compose.yml + +services: + + # ── PostgreSQL per a Nextcloud ─────────── + db: + container_name: nextcloud-db + image: postgres:16-alpine + restart: unless-stopped + environment: + TZ: Europe/Madrid + POSTGRES_DB: nextcloud + POSTGRES_USER: nextcloud + POSTGRES_PASSWORD: '${POSTGRES_PASSWORD}' + volumes: + - nc_db:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U nextcloud -d nextcloud"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + networks: [nc_net] + + # ── Redis per a cache i sessions ───────── + redis: + container_name: nextcloud-redis + image: redis:7.4-alpine + restart: unless-stopped + command: redis-server --requirepass '${REDIS_PASSWORD}' + healthcheck: + test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 15s + networks: [nc_net] + + # ── Nextcloud ──────────────────────────── + nextcloud: + container_name: nextcloud + image: nextcloud:33-apache # Actualitzat a darrera versió estable + restart: unless-stopped + environment: + TZ: Europe/Madrid + # Connexió a PostgreSQL + POSTGRES_HOST: db + POSTGRES_DB: nextcloud + POSTGRES_USER: nextcloud + POSTGRES_PASSWORD: '${POSTGRES_PASSWORD}' + # Connexió a Redis + REDIS_HOST: redis + REDIS_HOST_PASSWORD: '${REDIS_PASSWORD}' + # Compte administrador inicial (només s'usa en la primera instal·lació) + NEXTCLOUD_ADMIN_USER: '${NC_ADMIN_USER}' + NEXTCLOUD_ADMIN_PASSWORD: '${NC_ADMIN_PASSWORD}' + # Domini de confiança (obligatori!) + NEXTCLOUD_TRUSTED_DOMAINS: 'cloud.el-meu-domini.com' + # Configuració per a reverse proxy — genera URLs correctes + OVERWRITEPROTOCOL: https + OVERWRITECLIURL: 'https://cloud.el-meu-domini.com' + TRUSTED_PROXIES: '172.16.0.0/12' # Rang de xarxes Docker internes + # PHP — memòria i temps màxim + PHP_MEMORY_LIMIT: 512M + PHP_UPLOAD_LIMIT: 10G + volumes: + - nc_html:/var/www/html + - nc_data:/var/www/html/data + ports: + - "127.0.0.1:8081:80" # Només local → via Nginx Proxy Manager + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost/status.php"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 60s # Nextcloud triga a instal·lar-se la 1a vegada + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy + networks: [nc_net, shared_net] + + # ── Cron: tasques programades de Nextcloud ── + cron: + container_name: nextcloud-cron + image: nextcloud:33-apache # Mateixa versió que nextcloud! + restart: unless-stopped + entrypoint: /cron.sh + environment: + TZ: Europe/Madrid + volumes: + - nc_html:/var/www/html + - nc_data:/var/www/html/data + depends_on: + nextcloud: + condition: service_healthy # Espera que Nextcloud estigui llest + networks: [nc_net] + +volumes: + nc_db: + nc_html: + nc_data: + +networks: + nc_net: + driver: bridge + shared_net: + external: true + name: shared_net \ No newline at end of file diff --git a/ollama/docker-compose.yml b/ollama/docker-compose.yml new file mode 100644 index 0000000..7442911 --- /dev/null +++ b/ollama/docker-compose.yml @@ -0,0 +1,46 @@ +# /opt/stacks/ollama/docker-compose.yml + +services: + ollama: + container_name: ollama + image: ollama/ollama:latest + restart: unless-stopped + ports: + - "127.0.0.1:11434:11434" # API Ollama (només local, open-webui hi accedeix internament) + volumes: + - ollama_data:/root/.ollama # Models descarregats + environment: + TZ: Europe/Madrid + OLLAMA_HOST: 0.0.0.0 # Necessari perquè open-webui hi accedeixi via xarxa interna + networks: + - ollama-net + - shared_net + + open-webui: + container_name: open-webui + image: ghcr.io/open-webui/open-webui:main + restart: unless-stopped + ports: + - "127.0.0.1:3000:8080" # Interfície web (només local → via Nginx) + environment: + TZ: Europe/Madrid + OLLAMA_BASE_URL: http://ollama:11434 + WEBUI_SECRET_KEY: '${WEBUI_SECRET_KEY}' # Via .env + volumes: + - open_webui_data:/app/backend/data + depends_on: + - ollama + networks: + - ollama-net + - shared_net + +volumes: + ollama_data: + open_webui_data: + +networks: + ollama-net: + driver: bridge + shared_net: + external: true + name: shared_net \ No newline at end of file diff --git a/onlyoffice/docker-compose.yml b/onlyoffice/docker-compose.yml new file mode 100644 index 0000000..7e1b078 --- /dev/null +++ b/onlyoffice/docker-compose.yml @@ -0,0 +1,40 @@ +# Afegir al docker-compose.yml de Nextcloud +# /opt/stacks/nextcloud/docker-compose.yml + +services: + + # ... (db, redis, nextcloud, cron ja existents) ... + + # ── OnlyOffice Document Server ─────────── + onlyoffice: + container_name: onlyoffice + image: onlyoffice/documentserver:latest + restart: unless-stopped + environment: + TZ: Europe/Madrid + # JWT — ha de coincidir EXACTAMENT amb el plugin de Nextcloud + JWT_ENABLED: "true" + JWT_SECRET: '${ONLYOFFICE_JWT_SECRET}' + JWT_HEADER: Authorization # Valor per defecte oficial (no AuthorizationJwt) + ports: + - "8082:80" # Només local → via Nginx Proxy Manager + volumes: + - oo_data:/var/www/onlyoffice/Data # Certificats i configuració + - oo_logs:/var/log/onlyoffice # Logs + - oo_cache:/var/lib/onlyoffice # Cache de fitxers (OBLIGATORI!) + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost/healthcheck"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 60s # OnlyOffice triga a arrencar la 1a vegada + networks: [nc_net, shared_net] + +# Afegir als volumes existents: +volumes: + # ... (nc_db, nc_html, nc_data ja existents) ... + oo_data: + oo_logs: + oo_cache: # Nou — cache de fitxers + +# networks ja definides al compose de Nextcloud \ No newline at end of file diff --git a/pihole/docker-compose.yml b/pihole/docker-compose.yml new file mode 100644 index 0000000..e1501b8 --- /dev/null +++ b/pihole/docker-compose.yml @@ -0,0 +1,36 @@ +# /opt/stacks/pihole/docker-compose.yml + +services: + pihole: + container_name: pihole + image: pihole/pihole:latest + restart: unless-stopped + ports: + - "53:53/tcp" # DNS TCP — sense 127.0.0.1 (ha de ser accessible a tota la xarxa) + - "53:53/udp" # DNS UDP — ídem + - "127.0.0.1:8080:80" # Interfície web (només local → via Nginx) + environment: + TZ: 'Europe/Madrid' + WEBPASSWORD: '${PIHOLE_PASSWORD}' # Millor via .env + PIHOLE_DNS_: '9.9.9.9;149.112.112.112' + DNSSEC: 'true' + DNSMASQ_LISTENING: 'all' + volumes: + - pihole_config:/etc/pihole # Volum named — més estable + - pihole_dnsmasq:/etc/dnsmasq.d # Volum named — més estable + cap_add: + - NET_ADMIN + networks: + - dns-net + - shared_net + +volumes: + pihole_config: + pihole_dnsmasq: + +networks: + dns-net: + driver: bridge + shared_net: + external: true + name: shared_net \ No newline at end of file diff --git a/plex/docker-compose.yml b/plex/docker-compose.yml new file mode 100644 index 0000000..18af9af --- /dev/null +++ b/plex/docker-compose.yml @@ -0,0 +1,431 @@ +# /opt/stacks/plex/docker-compose.yml + +services: + + # ───────────────────────────────────────── + # PLEX + # ───────────────────────────────────────── + plex: + image: lscr.io/linuxserver/plex:latest + container_name: plex + restart: unless-stopped + network_mode: host # Necessari per a Plex discovery (mDNS/DLNA) + + group_add: + - "${VIDEO_GID:-44}" # grup 'video' per a GPU + - "${RENDER_GID:-992}" # grup 'render' per a /dev/dri + + environment: + PUID: ${PUID} + PGID: ${PGID} + TZ: ${TZ} + VERSION: docker + PLEX_CLAIM: ${PLEX_CLAIM} + + devices: + - /dev/dri/renderD128:/dev/dri/renderD128 + - /dev/dri/card0:/dev/dri/card0 + + volumes: + - ${DATA_DIR}/plex/config:/config + - ${RIVEN_MOUNT}:/mnt/riven:rslave + - ${PD_ZURG_MOUNT}:/mnt/pd_zurg:rslave + - ${TORBOX_MOUNT}:/mnt/torbox_zurg:rslave + - ${MEDIA_SERIES}:/mnt/media/series:ro + - ${MEDIA_MOVIES}:/mnt/media/pelis:ro + + depends_on: + - pd_zurg + - torbox_mount + + + # ───────────────────────────────────────── + # PD_ZURG — Real-Debrid + Zurg + rclone + # ───────────────────────────────────────── + pd_zurg: + image: iampuid0/pd_zurg:latest + container_name: pd_zurg + restart: unless-stopped + + stdin_open: true + tty: true + + devices: + - /dev/fuse:/dev/fuse:rwm + + cap_add: + - SYS_ADMIN + + security_opt: + - apparmor:unconfined + + environment: + TZ: ${TZ} + ZURG_ENABLED: "true" + RD_API_KEY: ${RD_API_KEY} + RCLONE_MOUNT_NAME: pd_zurg + PD_ENABLED: "true" + PLEX_USER: ${PLEX_USER} + PLEX_TOKEN: ${PLEX_TOKEN} + PLEX_ADDRESS: ${PLEX_ADDRESS} + + volumes: + - ${DATA_DIR}/pd_zurg/config:/config + - ${DATA_DIR}/pd_zurg/log:/log + - ${DATA_DIR}/pd_zurg/cache:/cache + - ${DATA_DIR}/pd_zurg/RD:/zurg/RD + - ${PD_ZURG_MOUNT}:/data:shared + + + # ───────────────────────────────────────── + # TORBOX MOUNT — Torbox WebDAV via rclone + # ───────────────────────────────────────── + torbox_mount: + image: rclone/rclone:latest + container_name: torbox_mount + restart: unless-stopped + + devices: + - /dev/fuse:/dev/fuse:rwm + + cap_add: + - SYS_ADMIN + + security_opt: + - apparmor:unconfined + + environment: + TZ: ${TZ} + + command: > + mount torbox: + /data + --config /config/rclone.conf + --allow-other + --allow-non-empty + --dir-cache-time 10s + --vfs-cache-mode full + --vfs-cache-max-size 512M + --no-modtime + --log-level INFO + + volumes: + - ${DATA_DIR}/torbox_mount/config:/config + - ${TORBOX_MOUNT}:/data:shared + + + # ───────────────────────────────────────── + # SEERR — Gestor de peticions + # ───────────────────────────────────────── + seerr: + image: ghcr.io/seerr-team/seerr:latest + container_name: seerr + restart: unless-stopped + + environment: + TZ: ${TZ} + + volumes: + - ${DATA_DIR}/seerr/config:/app/config + + ports: + - "5055:5055" # Web UI → via Nginx Proxy Manager + + networks: + - shared_net + + + # ───────────────────────────────────────── + # RIVEN — PostgreSQL + # ───────────────────────────────────────── + riven-postgres: + image: postgres:17-alpine + container_name: riven-postgres + restart: unless-stopped + + environment: + TZ: ${TZ} + POSTGRES_USER: ${RIVEN_POSTGRES_USER} + POSTGRES_PASSWORD: ${RIVEN_POSTGRES_PASSWORD} + POSTGRES_DB: ${RIVEN_POSTGRES_DB} + + volumes: + - riven_postgres_data:/var/lib/postgresql/data + + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${RIVEN_POSTGRES_USER} -d ${RIVEN_POSTGRES_DB}"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + + networks: + - riven_net + + + # ───────────────────────────────────────── + # RIVEN — Redis + # ───────────────────────────────────────── + riven-redis: + image: redis:7-alpine + container_name: riven-redis + restart: unless-stopped + + environment: + TZ: ${TZ} + + volumes: + - riven_redis_data:/data + + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 5s + retries: 5 + start_period: 10s + + networks: + - riven_net + + + # ───────────────────────────────────────── + # RIVEN — Backend API + # ───────────────────────────────────────── + riven: + image: ghcr.io/rivenmedia/riven-ts:sha-a367778 # Pinnat a SHA per estabilitat + container_name: riven + restart: unless-stopped + + tty: true + + cap_add: + - SYS_ADMIN + + security_opt: + - apparmor:unconfined + + devices: + - /dev/fuse:/dev/fuse + + extra_hosts: + - "host.docker.internal:host-gateway" + + environment: + TZ: ${TZ} + NODE_ENV: production # Era 'development' — canviat a production + RIVEN_SETTING__databaseUrl: "postgresql://${RIVEN_POSTGRES_USER}:${RIVEN_POSTGRES_PASSWORD}@riven-postgres:5432/${RIVEN_POSTGRES_DB}" + RIVEN_SETTING__redisUrl: "redis://riven-redis:6379" + RIVEN_SETTING__gqlHost: "0.0.0.0" + RIVEN_SETTING__gqlCorsOrigin: ${RIVEN_CORS_ORIGIN} + # ⚠️ CSRF desactivat — només acceptable en xarxa privada/VPN + RIVEN_SETTING__csrfPrevention: "false" + APOLLO_CSRF_PREVENTION_ENABLED: "false" + RIVEN_SETTING__enabledPlugins: '["stremthru","plex","tmdb","tvdb","seerr"]' + RIVEN_PLUGIN_SETTING__REPO_PLUGIN_TMDB__apiKey: ${TMDB_API_KEY} + RIVEN_PLUGIN_SETTING__REPO_PLUGIN_TVDB__apiKey: ${TVDB_API_KEY} + RIVEN_PLUGIN_SETTING__REPO_PLUGIN_STREMTHRU__realdebridApiKey: ${RD_API_KEY} + RIVEN_PLUGIN_SETTING__REPO_PLUGIN_STREMTHRU__torboxApiKey: ${TORBOX_API_KEY} + RIVEN_PLUGIN_SETTING__REPO_PLUGIN_PLEX__plexServerUrl: ${PLEX_ADDRESS} + RIVEN_PLUGIN_SETTING__REPO_PLUGIN_PLEX__plexToken: ${PLEX_TOKEN} + RIVEN_PLUGIN_SETTING__REPO_PLUGIN_SEERR__apiKey: ${SEERR_API_KEY} + RIVEN_PLUGIN_SETTING__REPO_PLUGIN_SEERR__url: ${SEERR_URL} + RIVEN_PLUGIN_SETTING__REPO_PLUGIN_SEERR__autofixMetadataProviders: "true" + RIVEN_SETTING__vfsMountPath: /mnt/riven + + volumes: + - riven_data:/riven/data + - ${RIVEN_MOUNT}:/mnt/riven:shared + + depends_on: + riven-postgres: + condition: service_healthy + riven-redis: + condition: service_healthy + seerr: + condition: service_started + pd_zurg: + condition: service_started + + networks: + - riven_net + - shared_net + + + # ───────────────────────────────────────── + # RIVEN — Frontend UI + # ───────────────────────────────────────── + riven-frontend: + image: ghcr.io/rivenmedia/riven-frontend:latest + container_name: riven-frontend + restart: unless-stopped + + tty: true + + environment: + TZ: ${TZ} + ORIGIN: ${RIVEN_ORIGIN} + BACKEND_URL: "http://riven:3000" + + volumes: + - riven_frontend_config:/riven/config + + depends_on: + riven: + condition: service_started + + networks: + - riven_net + - shared_net + + + # ───────────────────────────────────────── + # DMM — MySQL Database + # ───────────────────────────────────────── + dmm-db: + image: mysql:8.0 + container_name: dmm-db + restart: unless-stopped + + environment: + TZ: ${TZ} + MYSQL_ROOT_PASSWORD: ${DMM_DB_ROOT_PASSWORD} + MYSQL_DATABASE: dmm + MYSQL_USER: dmm + MYSQL_PASSWORD: ${DMM_DB_PASSWORD} + + volumes: + - dmm_db_data:/var/lib/mysql + + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${DMM_DB_ROOT_PASSWORD}"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + + networks: + - dmm_net + + + # ───────────────────────────────────────── + # DMM — Redis + # ───────────────────────────────────────── + dmm-redis: + image: redis:7-alpine + container_name: dmm-redis + restart: unless-stopped + command: redis-server --appendonly yes --maxmemory 128mb --maxmemory-policy allkeys-lru + + environment: + TZ: ${TZ} + + volumes: + - dmm_redis_data:/data + + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 5s + retries: 5 + start_period: 10s + + networks: + - dmm_net + + + # ───────────────────────────────────────── + # DMM — Tor proxy + # ───────────────────────────────────────── + dmm-tor: + image: debridmediamanager/tor + container_name: dmm-tor + restart: unless-stopped + + environment: + TZ: ${TZ} + + networks: + - dmm_net + + + # ───────────────────────────────────────── + # DMM — App (built from source) + # ───────────────────────────────────────── + dmm: + build: + context: ${DMM_BUILD_CONTEXT} # Era hardcoded /opt/tatsukai/plex/dmm + image: dmm-prod:local + container_name: dmm + restart: unless-stopped + + environment: + TZ: ${TZ} + DATABASE_URL: ${DATABASE_URL} + DMM_ORIGIN: ${DMM_URL} + DMMCAST_SALT: ${DMM_SECRET} + REDIS_URL: "redis://dmm-redis:6379" + TMDB_KEY: ${TMDB_API_KEY} + TRAKT_CLIENT_ID: ${TRAKT_CLIENT_ID} + TRAKT_CLIENT_SECRET: ${TRAKT_CLIENT_SECRET} + NEXT_PUBLIC_ANALYTICS_ENABLED: "false" + MDBLIST_KEY: ${MDBLIST_KEY} + TOR_PROXY: "socks5://dmm-tor:9050" + + ports: + - "3000:3000" # Web UI → via Nginx Proxy Manager + + depends_on: + dmm-db: + condition: service_healthy + dmm-redis: + condition: service_healthy # Era service_started — ara espera healthcheck + dmm-tor: + condition: service_started + + networks: + - dmm_net + - shared_net + + + # ───────────────────────────────────────── + # XTEVE — Proxy M3U / Virtual HDHomeRun + # ───────────────────────────────────────── + xteve: + image: alturismo/xteve:latest + container_name: xteve + restart: unless-stopped + + environment: + TZ: ${TZ} + XTEVE_TUNER: ${XTEVE_TUNER:-8} + + ports: + - "34400:34400" # Web UI → via Nginx Proxy Manager + + volumes: + - ${DATA_DIR}/xteve/config:/root/.xteve + - ${DATA_DIR}/xteve/temp:/tmp/xteve + - ${DATA_DIR}/epg/xmltv_master.xml:/root/xmltv.xml:ro + - ${DATA_DIR}/epg/master.m3u:/root/playlist.m3u:ro + + networks: + - shared_net + + +volumes: + riven_data: + riven_postgres_data: + riven_redis_data: + riven_frontend_config: + dmm_db_data: + dmm_redis_data: + + +networks: + riven_net: + driver: bridge + dmm_net: + driver: bridge + shared_net: + external: true + name: shared_net \ No newline at end of file diff --git a/portainer/docker-compose.yml b/portainer/docker-compose.yml new file mode 100644 index 0000000..204439e --- /dev/null +++ b/portainer/docker-compose.yml @@ -0,0 +1,24 @@ +# /opt/stacks/portainer/docker-compose.yml + +services: + portainer: + image: portainer/portainer-ce:latest + container_name: portainer + restart: unless-stopped + ports: + - "9000:9000" # Interfície web HTTP (només local) + - "9443:9443" # Interfície web HTTPS (només local) + # - "8000:8000" # Agent tunnel — descomenta si uses agents remots + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - portainer_data:/data + networks: + - shared_net + +volumes: + portainer_data: + +networks: + shared_net: + external: true + name: shared_net \ No newline at end of file diff --git a/postgres/docker-compose.yml b/postgres/docker-compose.yml new file mode 100644 index 0000000..a37cf96 --- /dev/null +++ b/postgres/docker-compose.yml @@ -0,0 +1,64 @@ +# /opt/stacks/postgres/docker-compose.yml + +services: + + # ── Servei PostgreSQL ──────────────────── + postgres: + container_name: postgres + image: postgres:16-alpine + restart: unless-stopped + environment: + TZ: Europe/Madrid + POSTGRES_USER: '${POSTGRES_USER}' + POSTGRES_PASSWORD: '${POSTGRES_PASSWORD}' + POSTGRES_DB: '${POSTGRES_DB}' + # UTF8 sense locale específic → compatible amb Alpine + POSTGRES_INITDB_ARGS: '--encoding=UTF8 --locale=en_US.utf8' + ports: + - "5432:5432" # Accés local per DBeaver, psql, etc. + volumes: + - postgres_data:/var/lib/postgresql/data + - postgres_init:/docker-entrypoint-initdb.d # Scripts SQL d'inicialització + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] + interval: 10s + timeout: 5s # Afegit + retries: 5 + start_period: 30s # Afegit: marge per a scripts d'init + networks: + - pg_net + + # ── pgAdmin: UI web per gestionar Postgres ── + pgadmin: + container_name: pgadmin + image: dpage/pgadmin4:latest + restart: unless-stopped + ports: + - "5050:80" # UI web (només local → via Nginx) + environment: + TZ: Europe/Madrid + PGADMIN_DEFAULT_EMAIL: '${PGADMIN_EMAIL}' + PGADMIN_DEFAULT_PASSWORD: '${PGADMIN_PASSWORD}' + PGADMIN_DISABLE_POSTFIX: 'true' # Evita errors de Postfix als logs + PGADMIN_CONFIG_SERVER_MODE: 'False' # Mode personal (un sol usuari) + PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: 'False' # Simplifica l'accés local + volumes: + - pgadmin_data:/var/lib/pgadmin # Persisteix servidors i preferències + depends_on: + postgres: + condition: service_healthy + networks: + - pg_net + - shared_net + +volumes: + postgres_data: + postgres_init: # Posa els teus scripts .sql aquí + pgadmin_data: # Configuració de pgAdmin persistent + +networks: + pg_net: + driver: bridge + shared_net: + external: true + name: shared_net diff --git a/redis/docker-compose.yml b/redis/docker-compose.yml new file mode 100644 index 0000000..ef8f795 --- /dev/null +++ b/redis/docker-compose.yml @@ -0,0 +1,70 @@ +# /opt/stacks/redis/docker-compose.yml + +services: + + # ── Servei Redis ───────────────────────── + redis: + container_name: redis + image: redis:7.4-alpine # Pinnat a LTS estable + restart: unless-stopped + command: + - redis-server + - --requirepass + - '${REDIS_PASSWORD}' + - --appendonly + - "yes" + - --maxmemory + - 256mb + - --maxmemory-policy + - allkeys-lru + - --loglevel + - notice + ports: + - "127.0.0.1:6379:6379" # Accés local per a clients externs + volumes: + - redis_data:/data + environment: + TZ: Europe/Madrid + healthcheck: + test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"] + interval: 10s + timeout: 5s # Afegit + retries: 5 + start_period: 15s # Afegit + networks: + - redis_net + + # ── RedisInsight: UI web per gestionar Redis ── + redisinsight: + container_name: redisinsight + image: redis/redisinsight:latest # Imatge oficial actualitzada + restart: unless-stopped + ports: + - "127.0.0.1:5540:5540" # Port correcte és 5540, no 8001! + environment: + TZ: Europe/Madrid + RI_ENCRYPTION_KEY: '${RI_ENCRYPTION_KEY}' # Xifra contrasenyes guardades + # Preconfiguració automàtica de la connexió al Redis local + RI_REDIS_HOST: redis + RI_REDIS_PORT: 6379 + RI_REDIS_ALIAS: Redis Local + RI_REDIS_PASSWORD: '${REDIS_PASSWORD}' + volumes: + - redisinsight_data:/data # Persisteix connexions i historial + depends_on: + redis: + condition: service_healthy + networks: + - redis_net + - shared_net + +volumes: + redis_data: + redisinsight_data: + +networks: + redis_net: + driver: bridge + shared_net: + external: true + name: shared_net \ No newline at end of file diff --git a/vaultwarden/docker-compose.yml b/vaultwarden/docker-compose.yml new file mode 100644 index 0000000..12cdff4 --- /dev/null +++ b/vaultwarden/docker-compose.yml @@ -0,0 +1,49 @@ +# /opt/stacks/vaultwarden/docker-compose.yml + +services: + + vaultwarden: + container_name: vaultwarden + image: vaultwarden/server:latest + restart: unless-stopped + environment: + TZ: Europe/Madrid + # Domini públic (obligatori per a links d'invitació i 2FA) + DOMAIN: 'https://vault.el-meu-domini.com' + # Seguretat: registre tancat + SIGNUPS_ALLOWED: "false" + # Token admin — usa hash argon2id (vegeu .env i instruccions sota) + ADMIN_TOKEN: '${VAULTWARDEN_ADMIN_TOKEN}' + # Configuració SMTP + SMTP_HOST: '${SMTP_HOST}' + SMTP_PORT: 587 + SMTP_SECURITY: starttls # Explícit: starttls | force_tls | off + SMTP_FROM: '${SMTP_FROM}' + SMTP_USERNAME: '${SMTP_USERNAME}' + SMTP_PASSWORD: '${SMTP_PASSWORD}' + SMTP_FROM_NAME: Vaultwarden + # Logs + LOG_LEVEL: warn + # WebSockets integrats al port principal des de v1.29.0 + # WEBSOCKET_ENABLED i port 3012 eliminats des de v1.31.0 + volumes: + - vaultwarden_data:/data + ports: + - "127.0.0.1:8080:80" # Port únic — WebSockets inclosos + # Port 3012 ELIMINAT — ja no existeix des de v1.31.0 + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:80/alive"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 15s + networks: + - shared_net + +volumes: + vaultwarden_data: + +networks: + shared_net: + external: true + name: shared_net \ No newline at end of file diff --git a/wg-easy/docker-compose.yml b/wg-easy/docker-compose.yml new file mode 100644 index 0000000..682c684 --- /dev/null +++ b/wg-easy/docker-compose.yml @@ -0,0 +1,38 @@ +# /opt/stacks/wg-easy/docker-compose.yml + +services: + wg-easy: + container_name: wg-easy + image: ghcr.io/wg-easy/wg-easy:latest + restart: unless-stopped + environment: + WG_HOST: '${WG_HOST}' # Domini o IP pública — via .env + PASSWORD_HASH: '${WG_PASSWORD_HASH}' # Hash bcrypt — via .env + WG_PORT: 51820 + WG_DEFAULT_ADDRESS: '10.0.0.1' # Adreça del servidor VPN (no .x) + WG_DEFAULT_DNS: '${WG_DNS}' # AdGuard/Pi-hole — via .env + WG_ALLOWED_IPS: '0.0.0.0/0' + UI_TRAFFIC_STATS: true + UI_CHART_TYPE: 1 + TZ: Europe/Madrid + ports: + - "51820:51820/udp" # WireGuard VPN — públic (necessari per a clients) + - "51821:51821" # Interfície web (només local → via Nginx) + volumes: + - wg_easy_data:/etc/wireguard # Volum named — configuració persistent + cap_add: + - NET_ADMIN + - SYS_MODULE + sysctls: + - net.ipv4.ip_forward=1 + - net.ipv4.conf.all.src_valid_mark=1 + networks: + - shared_net + +volumes: + wg_easy_data: + +networks: + shared_net: + external: true + name: shared_net \ No newline at end of file diff --git a/wordpress/docker-compose.yml b/wordpress/docker-compose.yml new file mode 100644 index 0000000..3bc3a65 --- /dev/null +++ b/wordpress/docker-compose.yml @@ -0,0 +1,92 @@ +# /opt/stacks/wordpress/docker-compose.yml + +services: + + # ── MariaDB ────────────────────────────── + db: + container_name: wordpress-db + image: mariadb:10.11 + restart: unless-stopped + environment: + TZ: Europe/Madrid + MYSQL_ROOT_PASSWORD: '${MYSQL_ROOT_PASSWORD}' + MYSQL_DATABASE: wordpress + MYSQL_USER: wordpress + MYSQL_PASSWORD: '${MYSQL_PASSWORD}' + volumes: + - wp_db:/var/lib/mysql + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + networks: [wp_net] + + # ── Redis — cache d'objectes ───────────── + # Nota: Redis és volàtil per defecte — la cache es perd en reiniciar. + # Per a WordPress és acceptable (es reconstrueix automàticament). + redis: + container_name: wordpress-redis + image: redis:7-alpine + restart: unless-stopped + environment: + TZ: Europe/Madrid + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s + networks: [wp_net] + + # ── WordPress ──────────────────────────── + wordpress: + container_name: wordpress + image: wordpress:6-apache + restart: unless-stopped + environment: + TZ: Europe/Madrid + # Connexió a MariaDB + WORDPRESS_DB_HOST: db + WORDPRESS_DB_NAME: wordpress + WORDPRESS_DB_USER: wordpress + WORDPRESS_DB_PASSWORD: '${MYSQL_PASSWORD}' + # Claus de seguretat — les 8 obligatòries + # Genera-les a: https://api.wordpress.org/secret-key/1.1/salt/ + WORDPRESS_AUTH_KEY: '${WP_AUTH_KEY}' + WORDPRESS_SECURE_AUTH_KEY: '${WP_SECURE_AUTH_KEY}' + WORDPRESS_LOGGED_IN_KEY: '${WP_LOGGED_IN_KEY}' + WORDPRESS_NONCE_KEY: '${WP_NONCE_KEY}' + WORDPRESS_AUTH_SALT: '${WP_AUTH_SALT}' + WORDPRESS_SECURE_AUTH_SALT: '${WP_SECURE_AUTH_SALT}' + WORDPRESS_LOGGED_IN_SALT: '${WP_LOGGED_IN_SALT}' + WORDPRESS_NONCE_SALT: '${WP_NONCE_SALT}' + # Configuració addicional via wp-config + WORDPRESS_CONFIG_EXTRA: | + define('WP_REDIS_HOST', 'redis'); + define('WP_REDIS_PORT', 6379); + define('WP_CACHE', true); + define('WP_HOME', '${WP_URL}'); + define('WP_SITEURL', '${WP_URL}'); + volumes: + - wp_data:/var/www/html + ports: + - "8086:80" + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy # Afegit — WordPress necessita Redis disponible + networks: [wp_net, shared_net] + +volumes: + wp_db: + wp_data: + +networks: + wp_net: + driver: bridge + shared_net: + external: true + name: shared_net \ No newline at end of file