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.
Cómo encaja con Plesk
Sección titulada «Cómo encaja con Plesk»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.
1. Requisitos
Sección titulada «1. Requisitos»- En el servidor: Docker Engine y el plugin
docker compose(v2), más acceso SSH normal. Los puertos elegidos libres en127.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.
2. Primer despliegue (en el servidor)
Sección titulada «2. Primer despliegue (en el servidor)»Crea una carpeta en el servidor para tener todo junto (esta guía usa repoferry, pero vale cualquier nombre):
mkdir -p repoferry && cd repoferrySolo 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):
MONGO_ROOT_USERNAME=repoferryMONGO_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=latestObligatorias:
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 conopenssl rand -base64 32.JWT_SECRET— clave de firma de las sesiones de login (HMAC-SHA256, mínimo 32 bytes). Genera una conopenssl 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. DejaSMTP_HOSTsin 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 defecto8080/8081/8082.IMAGE_TAG— qué tag de imagen de Docker Hub ejecutar. Por defectolatest.
Después, en la misma carpeta:
mkdir -p data/mongodb # bind mount para los datos de MongoDB -- quedan como ficheros normales en el hostchmod 600 .env # contiene secretos realesNo 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:
docker compose -f compose.yaml --env-file .env pulldocker compose -f compose.yaml --env-file .env up -ddocker 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.
3. Configurar los dominios en Plesk
Sección titulada «3. Configurar los dominios en Plesk»Para cada uno de los tres dominios:
-
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
appyapicomo 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:
apioapp. - 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.
- Subdomain name:
-
-
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.comque cubra*.repoferry.com(así cubre tambiénapp.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.comRecord:<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 subdominios —
app.repoferry.com/api.repoferry.comseguirá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. -
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:- Abre Docker Proxy Rules y pulsa Add rule.
- URL: tu dominio con barra final, p.ej.
repoferry.com/. - Container: elige el contenedor correspondiente del stack que acabas de levantar (se llaman
repoferry-prod-<servicio>-1, ya que arribacompose.yamlfijaname: repoferry-prod) — ver la tabla de abajo. - 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. - Support Websocket traffic: márcalo (necesario para frontend/backend; inofensivo para el sitio estático).
- Pulsa OK.
Repite una regla por dominio:
Dominio Contenedor Mapeo de puerto repoferry.comrepoferry-prod-website-180 -> 8082app.repoferry.comrepoferry-prod-frontend-180 -> 8081api.repoferry.comrepoferry-prod-backend-18080 -> 8080
4. Completar la instalación
Sección titulada «4. Completar la instalación»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.
Datos y copias de seguridad
Sección titulada «Datos y copias de seguridad»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:
docker compose -f compose.yaml exec mongodb \ mongodump --username "$MONGO_ROOT_USERNAME" --password "$MONGO_ROOT_PASSWORD" --authenticationDatabase admin --archive \ > repoferry-$(date +%F).archiveComo 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:
docker run --rm -v "$(pwd)/data/mongodb:/target" alpine sh -c "rm -rf /target/*"Notas de seguridad
Sección titulada «Notas de seguridad»- MongoDB nunca es alcanzable fuera de la red interna de Docker.
.envcontiene secretos reales: mantenlo conchmod 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 psmuestra su estado.