Tots els compose

This commit is contained in:
Guillem Hernandez Sola
2026-06-30 13:12:37 +02:00
parent bebb2969f8
commit f1fe15d1b5
35 changed files with 2075 additions and 0 deletions

View File

@@ -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

17
astro/Dockerfile Normal file
View File

@@ -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

28
astro/docker-compose.yml Normal file
View File

@@ -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

16
astro/nginx.conf Normal file
View File

@@ -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";
}
}

View File

@@ -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

View File

@@ -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

23
beszel/docker-compose.yml Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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

83
gitea/docker-compose.yml Normal file
View File

@@ -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 <gitea@domini.com>'
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

21
glance/docker-compose.yml Normal file
View File

@@ -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

10
glance/glance.yml Normal file
View File

@@ -0,0 +1,10 @@
server:
port: 8080
pages:
- name: Inici
columns:
- size: full
widgets:
- type: clock
hour-format: 24h

15
headscale/config.yml Normal file
View File

@@ -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

View File

@@ -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

28
homarr/docker-compose.yml Normal file
View File

@@ -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

112
immich/docker-compose.yml Normal file
View File

@@ -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

View File

@@ -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 <host_user>
# 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

27
kavita/docker-compose.yml Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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

100
mautic/docker-compose.yml Normal file
View File

@@ -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

View File

@@ -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

62
moodle/docker-compose.yml Normal file
View File

@@ -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

64
n8n/docker-compose.yml Normal file
View File

@@ -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

View File

@@ -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

46
ollama/docker-compose.yml Normal file
View File

@@ -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

View File

@@ -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

36
pihole/docker-compose.yml Normal file
View File

@@ -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

431
plex/docker-compose.yml Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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

70
redis/docker-compose.yml Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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