Ir al contenido

Docker Compose + Plesk

Esta es una de varias formas de instalar RepoFerry (las demás — compilar desde el código fuente, generar tus propias imágenes Docker, o Docker Compose sin Plesk — se documentarán aquí también según se vayan escribiendo). Esta guía cubre Docker Compose detrás de Plesk: RepoFerry se distribuye como tres imágenes Docker (backend, frontend, website) más MongoDB, y el servidor de producción nunca compila nada — las imágenes se construyen y publican desde una máquina de desarrollo, y el servidor solo las descarga y ejecuta.

Esta guía asume que Plesk ya gestiona el servidor y hace de proxy inverso y terminador TLS — igual que para cualquier otro sitio en la misma máquina.

El stack de Docker no gestiona dominios ni TLS en absoluto: cada servicio expone HTTP plano en un puerto local (127.0.0.1:<puerto>), y Plesk hace de proxy inverso con un certificado Let’s Encrypt. Puertos por defecto:

Dominio Servicio Puerto local
https://repoferry.com website 127.0.0.1:8082
https://app.repoferry.com frontend 127.0.0.1:8081
https://api.repoferry.com backend 127.0.0.1:8080

Los puertos son configurables (BACKEND_PORT, FRONTEND_PORT, WEBSITE_PORT en .env) por si alguno choca con otro servicio que ya corra en el servidor. MongoDB nunca se expone en ningún puerto del host — solo backend puede alcanzarla, a través de la red interna de Docker.

  • En el servidor: Docker Engine y el plugin docker compose (v2), más acceso SSH normal. Los puertos elegidos libres en 127.0.0.1.
  • En Plesk: la extensión Docker instalada (Extensions → busca “Docker” → Install) — se usa más adelante para hacer proxy inverso de cada dominio a su contenedor sin editar nginx a mano.

Crea una carpeta en el servidor para tener todo junto (esta guía usa repoferry, pero vale cualquier nombre):

Terminal window
mkdir -p repoferry && cd repoferry

Solo necesitas dos ficheros dentro — no hace falta clonar el repositorio entero. Crea compose.yaml con este contenido:

name: repoferry-prod
services:
mongodb:
image: mongo:8.3.4
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: ${MONGO_ROOT_USERNAME}
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_ROOT_PASSWORD}
volumes:
- ./data/mongodb:/data/db
networks:
- internal
healthcheck:
test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand('ping')"]
interval: 10s
timeout: 5s
retries: 5
deploy:
resources:
limits:
memory: 768m
backend:
image: pepesan/repoferry-backend:${IMAGE_TAG:-latest}
restart: always
environment:
- SPRING_PROFILES_ACTIVE=prod
- MONGODB_URI=mongodb://${MONGO_ROOT_USERNAME}:${MONGO_ROOT_PASSWORD}@mongodb:27017/repoferry?authSource=admin
- JWT_SECRET=${JWT_SECRET}
- SECRETS_ENCRYPTION_KEY=${SECRETS_ENCRYPTION_KEY}
- CORS_ALLOWED_ORIGINS=https://app.repoferry.com
# Sin "=valor": Compose las pasa solo si están definidas en .env, en vez de
# fijarlas a cadena vacía si no lo están -- importante para SMTP_PORT (un
# int en el backend; una cadena vacía rompería el arranque), y para que el
# backend las trate como "no definidas" en vez de "definidas pero vacías".
- REGISTRATION_ENABLED
- APP_PUBLIC_URL
- SMTP_HOST
- SMTP_PORT
- SMTP_USERNAME
- SMTP_PASSWORD
- SMTP_FROM
# Bootstrap desatendido del admin: las tres deben venir juntas, o ninguna.
- ADMIN_EMAIL
- ADMIN_USERNAME
- ADMIN_PASSWORD
depends_on:
mongodb:
condition: service_healthy
networks:
- internal
ports:
# Solo accesible desde el propio servidor; Plesk hace de proxy inverso.
- "127.0.0.1:${BACKEND_PORT:-8080}:8080"
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:8080/actuator/health"]
interval: 10s
timeout: 5s
start_period: 30s
retries: 5
deploy:
resources:
limits:
memory: 512m
frontend:
image: pepesan/repoferry-frontend:${IMAGE_TAG:-latest}
restart: always
ports:
- "127.0.0.1:${FRONTEND_PORT:-8081}:80"
deploy:
resources:
limits:
memory: 128m
website:
image: pepesan/repoferry-website:${IMAGE_TAG:-latest}
restart: always
ports:
- "127.0.0.1:${WEBSITE_PORT:-8082}:80"
deploy:
resources:
limits:
memory: 128m
networks:
internal:

Y .env al lado (sustituye CORS_ALLOWED_ORIGINS/APP_PUBLIC_URL en ambos ficheros si usas un dominio distinto de repoferry.com):

Terminal window
MONGO_ROOT_USERNAME=repoferry
MONGO_ROOT_PASSWORD=
JWT_SECRET=
SECRETS_ENCRYPTION_KEY=
# REGISTRATION_ENABLED=true
# APP_PUBLIC_URL=https://app.repoferry.com
# SMTP_HOST=smtp.example.com
# SMTP_PORT=587
# SMTP_USERNAME=
# SMTP_PASSWORD=
# SMTP_FROM=no-reply@repoferry.com
# ADMIN_EMAIL=admin@example.com
# ADMIN_USERNAME=admin
# ADMIN_PASSWORD=
# BACKEND_PORT=8080
# FRONTEND_PORT=8081
# WEBSITE_PORT=8082
# IMAGE_TAG=latest

Obligatorias:

  • MONGO_ROOT_USERNAME / MONGO_ROOT_PASSWORD — usuario root de MongoDB, creado por el propio contenedor en el primer arranque. Cualquier usuario vale; genera una contraseña fuerte con openssl rand -base64 32.
  • JWT_SECRET — clave de firma de las sesiones de login (HMAC-SHA256, mínimo 32 bytes). Genera una con openssl rand -base64 48. Cambiarla más adelante desconecta a todo el mundo (invalida todas las sesiones activas).
  • SECRETS_ENCRYPTION_KEY — cifra en reposo los secretos guardados en la base de datos (hoy, la contraseña SMTP). Genera una distinta de la misma forma: openssl rand -base64 48.

Opcionales — puedes dejarlas comentadas y rellenarlas después desde el wizard de instalación o /admin/settings:

  • REGISTRATION_ENABLED (true/false) — permite el autorregistro público. Desactivado por defecto.
  • APP_PUBLIC_URL — URL pública de tu frontend, usada para enlaces absolutos en emails.
  • SMTP_HOST/SMTP_PORT/SMTP_USERNAME/SMTP_PASSWORD/SMTP_FROM — relay SMTP para emails transaccionales. Deja SMTP_HOST sin definir para configurarlo después en su lugar.
  • ADMIN_EMAIL/ADMIN_USERNAME/ADMIN_PASSWORD — bootstrap desatendido del admin: define las tres (nunca solo una o dos) para que la cuenta de administrador se cree automáticamente en el primer arranque, sin pasar por el wizard.
  • BACKEND_PORT/FRONTEND_PORT/WEBSITE_PORT — puertos locales hacia los que Plesk hace proxy inverso. Por defecto 8080/8081/8082.
  • IMAGE_TAG — qué tag de imagen de Docker Hub ejecutar. Por defecto latest.

Después, en la misma carpeta:

Terminal window
mkdir -p data/mongodb # bind mount para los datos de MongoDB -- quedan como ficheros normales en el host
chmod 600 .env # contiene secretos reales

No hace falta hacer chown/chmod de data/mongodb para MongoDB: la imagen oficial arranca como root y toma posesión de la carpeta automáticamente en el primer arranque (con su propio usuario interno mongodb, UID 999), sea quien sea quien la haya creado. Por eso mismo, borrarla más adelante necesita un contenedor efímero en vez de un rm -rf normal — ver Datos y copias de seguridad más abajo.

Descarga las imágenes y levanta el stack:

Terminal window
docker compose -f compose.yaml --env-file .env pull
docker compose -f compose.yaml --env-file .env up -d
docker compose -f compose.yaml --env-file .env ps # comprueba que los 4 contenedores están "healthy"

Para actualizar más adelante (tras publicar una nueva imagen), vuelve a ejecutar los comandos pull y up -d de arriba.

Para cada uno de los tres dominios:

  1. Añade el dominio/subdominio si no existe todavía, apuntando su DNS al servidor:

    • Si el dominio principal (repoferry.com) tampoco está dado de alta en Plesk, añádelo primero como dominio normal (Websites & Domains → Add Domain).

    • Después añade app y api como subdominios bajo él — hazlo antes de intentar añadir una Docker Proxy Rule para ellos, ya que la regla necesita un dominio/subdominio existente al que asociarse. En Websites & Domains → Add Subdomain:

      • Subdomain name: api o app.
      • Select the domain: tu dominio principal (repoferry.com).
      • Document root: deja el sugerido (api.repoferry.com / app.repoferry.com).
      • Pulsa OK — debería aparecer en el listado de Websites & Domains.
  2. SSL/TLS Certificates: emite un certificado Let’s Encrypt (Plesk lo renueva automáticamente). En vez de tres certificados separados, puedes emitir un único certificado wildcard para repoferry.com que cubra *.repoferry.com (así cubre también app.repoferry.com/api.repoferry.com) — más sencillo de mantener, y es lo que asumen estas instrucciones a partir de aquí.

    Los certificados wildcard exigen el reto ACME DNS-01 en vez del habitual por HTTP, porque Let’s Encrypt no tiene forma de alcanzar por HTTP cualquier subdominio posible. Plesk se detendrá a mitad de la solicitud y mostrará algo así:

    Please wait while Plesk finishes adding a DNS record with the following parameters: Record type: TXT Domain name: _acme-challenge.repoferry.com Record: <un token largo>

    Tienes que crear tú ese registro TXT exacto en tu proveedor de DNS (p.ej. Dinahosting) antes de pulsar “Continue” — Plesk no lo crea por ti, solo te dice qué necesita encontrar. Si tu proveedor de DNS tarda en propagar, dale un minuto o dos antes de continuar; si Plesk da timeout, normalmente puedes reintentar sin empezar de cero.

    Emitir el wildcard en el dominio principal no lo aplica automáticamente a los subdominiosapp.repoferry.com/api.repoferry.com seguirán sirviendo el certificado autofirmado por defecto de Plesk hasta que lo arregles en cada uno: abre el subdominio → pestaña SSL/TLS Certificates, y emite un certificado para él directamente (el texto exacto varía según la versión de Plesk — puede que veas “Secure the domain name” → “Get it Free”, o “Install a free basic certificate provided by Let’s Encrypt” → “Install”). Esto te deja elegir el wildcard ya existente o emite un certificado nuevo solo para ese subdominio — cualquiera de los dos sustituye al autofirmado.

  3. Proxy inverso hacia el contenedor, usando la pantalla Docker Proxy Rules de la extensión Docker de Plesk — detecta los contenedores igual aunque se hayan arrancado con docker compose, no con la propia extensión:

    1. Abre Docker Proxy Rules y pulsa Add rule.
    2. URL: tu dominio con barra final, p.ej. repoferry.com/.
    3. Container: elige el contenedor correspondiente del stack que acabas de levantar (se llaman repoferry-prod-<servicio>-1, ya que arriba compose.yaml fija name: repoferry-prod) — ver la tabla de abajo.
    4. Port: el puerto interno del contenedor mapeado a su puerto publicado en el host (la propia extensión ya te lo muestra), p.ej. 80 -> 8082.
    5. Support Websocket traffic: márcalo (necesario para frontend/backend; inofensivo para el sitio estático).
    6. Pulsa OK.

    Repite una regla por dominio:

    Dominio Contenedor Mapeo de puerto
    repoferry.com repoferry-prod-website-1 80 -> 8082
    app.repoferry.com repoferry-prod-frontend-1 80 -> 8081
    api.repoferry.com repoferry-prod-backend-1 8080 -> 8080

Visita el dominio del frontend. Como es el primer arranque (sin usuarios todavía en MongoDB), verás el wizard de instalación para crear la cuenta de administrador — salvo que hayas rellenado ADMIN_EMAIL/ADMIN_USERNAME/ADMIN_PASSWORD en .env, en cuyo caso el admin ya existe y verás directamente la pantalla de login.

Los datos de MongoDB viven como bind mount (./data/mongodb, junto a compose.yaml) — ficheros normales en el host, fáciles de incluir en la rutina de backups que ya tenga el servidor. Para un volcado lógico puntual, ejecuta esto desde la misma carpeta que compose.yaml:

Terminal window
docker compose -f compose.yaml exec mongodb \
mongodump --username "$MONGO_ROOT_USERNAME" --password "$MONGO_ROOT_PASSWORD" --authenticationDatabase admin --archive \
> repoferry-$(date +%F).archive

Como el entrypoint de MongoDB toma posesión de data/mongodb como su propio usuario interno (UID 999), un rm -rf data/mongodb normal en el host fallará con “Permiso denegado” en cuanto el stack haya corrido al menos una vez. Bórrala vía un contenedor efímero en su lugar:

Terminal window
docker run --rm -v "$(pwd)/data/mongodb:/target" alpine sh -c "rm -rf /target/*"
  • MongoDB nunca es alcanzable fuera de la red interna de Docker.
  • .env contiene secretos reales: mantenlo con chmod 600 (como se ha hecho arriba) y nunca lo commitees si lo llevas en control de versiones.
  • El backend arranca con SPRING_PROFILES_ACTIVE=prod, que desactiva la documentación Swagger UI/OpenAPI.
  • Los tres contenedores de aplicación llevan su propio HEALTHCHECK; docker compose ps muestra su estado.