---
title: "Instalar Quire con Docker Compose"
description: "Instala Quire en tu propia infraestructura con Docker Compose."
image: "https://docs.quirelms.com/og.png"
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.quirelms.com/es/llms.txt
> Use this file to discover all available pages before exploring further.

# Instalar Quire con Docker Compose

<span id="installing-quire-with-docker-compose"></span>

Este es el producto completo en un solo host: el LMS, sus trabajos en segundo plano, los servicios de edición colaborativa y en tiempo real, y todos los servicios opcionales detrás de perfiles. El diseño se describe en la sección 2 de `docs/architecture/23-ops.md`.

Otros destinos: [Vercel](/es/ops/vercel/) y [Cloudflare Workers](/es/ops/cloudflare/) ejecutan solo la capa web. Consulta [upgrade.md](/es/ops/upgrade/) para las actualizaciones y [backup-restore.md](/es/ops/backup-restore/) para las copias de seguridad y el simulacro de restauración.

## Requisitos <!--quire:what-you-need-->

- Docker Engine 27 o posterior, con el complemento Compose 2.30 o posterior.
- 4 núcleos de CPU y 8 GB de memoria para la pila predeterminada; 8 núcleos y 16 GB con `--profile full` (solo ClamAV ocupa aproximadamente 1,5 GB para las firmas).
- Un nombre DNS para la capa web y otro para el contenido no confiable. Deben ser hosts distintos: los paquetes SCORM y el HTML cargado se ejecutan en el origen de contenido, para que nunca puedan leer las cookies del LMS.
- Para una prueba local, `lvh.me` y `*.localhost` resuelven a 127.0.0.1, que es lo que configura `docker/.env.example`. El servicio `proxy` de la pila sirve ambos por https con una autoridad de certificación local, por lo que no hay que instalar nada más (consulta «TLS»).
- Los puertos 80 y 443 deben estar libres en el host (`QUIRE_PROXY_HTTP_PORT` y `QUIRE_PROXY_HTTPS_PORT` permiten cambiarlos).

## Primera ejecución <!--quire:first-run-->

```sh
QUIRE_APP_ORIGIN=https://learn.example.org \
QUIRE_CONTENT_ORIGIN=https://content.example-content.org \
QUIRE_SETUP_ADMIN_EMAIL=you@example.org \
  docker/scripts/init-env.sh
docker compose -f docker/compose.yaml up -d --build
docker compose -f docker/compose.yaml logs init
```

`docker/scripts/init-env.sh` genera `docker/.env` a partir de `docker/.env.example`, crea todos los secretos (contraseñas de la base de datos, claves de firma y maestra, el par de claves para lanzar contenido) y guarda la clave de firma del punto de control de auditoría en `docker/secrets/audit-signing-key.pem`, que Compose monta en los workers como secreto. Solo requiere `sh`, `awk` y `openssl`, y se niega a sobrescribir un `docker/.env` existente. Copia ambos archivos fuera del host: sin `QUIRE_MASTER_KEY`, una base de datos restaurada no puede descifrar las credenciales almacenadas. Para rellenar el archivo manualmente, ejecuta `cp docker/.env.example docker/.env`; el archivo explica cómo generar cada secreto.

Ambos orígenes deben usar `https`: el servicio de contenido rechaza http sin cifrar en producción y no pueden compartir un mismo dominio registrable. El servicio `proxy` termina TLS para ambos (consulta «TLS»); `init-env.sh` rechaza un origen `http://`.

La pila se inicia en un orden fijo y cada paso espera a que termine el anterior:

1. `postgres` queda en estado saludable. En el primer inicio, su script de inicialización (`docker/postgres/init/90-passwords.sh`) establece las contraseñas de los cuatro roles.
2. `migrate` aplica todas las migraciones e inicializa la cola de trabajos en la base de datos de control y en cada base de datos de tenant dedicada, comprueba que todas coincidan y luego termina (consulta docs/ops/upgrade.md). Las migraciones se ejecutan en cada inicio y son idempotentes, así que para actualizar basta con una nueva imagen y un reinicio.
3. `init` (`apps/web/src/first-run.ts`) registra la base de datos de la aplicación en `QUIRE_DATABASE_ID` y, cuando se establece `QUIRE_SETUP_ADMIN_EMAIL`, crea la primera organización y su administrador. La dirección de inicio de sesión y una contraseña generada se muestran una sola vez en `docker compose logs init`.
4. Se inician `web`, `content`, `worker`, `scheduler`, `collab` y `centrifugo`.
5. `proxy` se inicia cuando `web` y `content` están saludables.

Abre `https://demo.` seguido del dominio de la aplicación (el registro de `init` muestra la dirección exacta de inicio de sesión) e inicia sesión. En una instalación local, primero confía en la autoridad de certificación del proxy (consulta «TLS»). Cambia la contraseña generada en `/account/security`.

Si un proceso no dispone de un secreto obligatorio, se niega a iniciarse e indica en el registro cuál falta. Nada se inicia con una configuración incompleta.

## Servicios y perfiles <!--quire:services-and-profiles-->

| Servicio | Perfil | Función |
| --- | --- | --- |
| postgres | siempre | La base de datos (PostgreSQL 18 con pgvector, compilada desde `docker/postgres.Dockerfile`), con archivado WAL desde el primer inicio |
| migrate, init | siempre | Tareas únicas: migraciones y, después, primera ejecución |
| web | siempre | El LMS, en `QUIRE_HTTP_PORT` (8080) |
| content | siempre | El origen de contenido no confiable, en `QUIRE_CONTENT_PORT` (8081) |
| worker | siempre | Trabajos en segundo plano: correo, informes, procesamiento de archivos y webhooks |
| scheduler | siempre | Trabajos periódicos: registra las 64 programaciones de ejecución y las entrega al worker; solo puede haber un líder a la vez |
| collab | siempre | WebSocket para la edición colaborativa, en `QUIRE_COLLAB_HTTP_PORT` (1234) |
| centrifugo | siempre | Distribución en tiempo real, en `QUIRE_REALTIME_PORT` (8000) |
| proxy | siempre | Caddy, la entrada TLS en los puertos 80 y 443 (consulta «TLS») |
| valkey | `cache` | Caché y límites de solicitudes |
| clamav | `scan` | Análisis de malware en los archivos cargados |
| gotenberg | `preview` | Vistas previas de Office a PDF y creación de certificados |
| imgproxy | `images` | Imágenes redimensionadas y convertidas |
| transcoder | `video` | La imagen del worker con ffmpeg únicamente LGPL para generar versiones de vídeo |
| seaweedfs | `storage` | Almacenamiento de objetos compatible con S3 en este host |
| otelcol | `observability` | Un colector de OpenTelemetry |
| mailpit | `devmail` | Captura todo el correo saliente para probar Quire |
| backup | `backup` | Copia de seguridad base de una sola ejecución; consulta backup-restore.md |
| backup-scheduler, backup-offsite | `backup` | Copia base cada `QUIRE_BACKUP_INTERVAL_HOURS` y copias cifradas externas al host, con un simulacro de verificación semanal |
| h5p | `h5p` | La imagen de herramienta LTI 1.3 de H5P que proporcionas mediante `QUIRE_H5P_IMAGE`, en `QUIRE_H5P_PORT` (8090); consulta «Conectar un proveedor H5P» |

`--profile full` inicia todos los servicios opcionales, salvo `backup` y `h5p`. Para iniciar uno, ejecuta `docker compose -f docker/compose.yaml --profile scan up -d`. Quire sigue funcionando sin un servicio opcional y explica qué falta: sin analizador, los archivos se guardan sin analizar y se avisa al administrador; sin Gotenberg, los archivos se pueden descargar en lugar de previsualizarse; sin transcodificador, el vídeo se reproduce como archivo original.

Todas las imágenes de terceros y sus obligaciones de licencia se enumeran en `docker/third-party-containers.yaml`.

### Conectar un proveedor H5P <!--quire:connecting-an-h5p-provider-->

Quire no integra ni incluye un entorno de ejecución de H5P ni un componente auxiliar (ADR 0019). Si usas H5P, proporciona tu propia suscripción alojada u opera una instancia autoalojada de H5P por separado de Quire. Registra ese proveedor como herramienta externa LTI 1.3 y añade su contenido a los cursos como actividades de herramienta. Quire intercambia calificaciones y el progreso de la actividad y la evaluación mediante LTI Assignment and Grade Services (AGS). Si el proveedor también envía declaraciones xAPI, configura por separado su envío al almacén de declaraciones xAPI de Quire; el intercambio de calificaciones y progreso de AGS no envía declaraciones xAPI. Las importaciones de Moodle indican que las actividades H5P necesitan una conexión con una herramienta LTI. El proveedor sigue siendo responsable de su entorno de ejecución H5P, la creación de contenido, el banco de contenido y el historial de intentos.

Para ejecutar tu propia instancia autoalojada en este host, establece `QUIRE_H5P_IMAGE` en su imagen e inicia el perfil `h5p`. Compose la publica en `QUIRE_H5P_PORT` (8090) y guarda sus datos en el volumen `h5p-data`; la imagen y las obligaciones asociadas siguen siendo tuyas.

## Configuración <!--quire:settings-->

Todos los procesos leen `docker/.env`. La plantilla `docker/.env.example` enumera cada parámetro y su valor predeterminado. Estos son los grupos:

### Direcciones <!--quire:addresses-->

| Parámetro | Significado |
| --- | --- |
| `QUIRE_APP_ORIGIN` | La dirección pública del LMS, por ejemplo, `https://learn.example.com` |
| `QUIRE_CONTENT_ORIGIN` | El origen del contenido, en un host distinto |
| `QUIRE_PLATFORM_DOMAINS` | Dominios que alojan organizaciones, separados por comas |
| `QUIRE_MARKETING_ORIGIN` | Optional. The marketing site, default `https://quirelms.com`. The only origin the waitlist form (`POST /api/waitlist`, `POST /waitlist`) accepts and redirects to. Comma separated; a `www.` variant is allowed only if listed |
| `QUIRE_DEPLOY_TARGET` | Aquí, `compose`. Consulta las otras guías para `vercel` y `cloudflare` |
| `QUIRE_TRUSTED_PROXY_CIDRS` | Proxies cuyos valores de `X-Forwarded-For` se consideran fiables |

### Secretos <!--quire:secrets-->

| Parámetro | Significado |
| --- | --- |
| `QUIRE_SECRET_KEY` | Firma sesiones y tokens. 64 caracteres hexadecimales |
| `QUIRE_MASTER_KEY` | Protege credenciales almacenadas, como secretos de SSO y webhooks. 32 bytes, en base64. La capa web y el worker deben usar el mismo valor. Rotación: [key-rotation.md](/es/ops/key-rotation/) |
| `QUIRE_MASTER_KEY_VERSION` | Etiqueta de versión de la clave maestra; `v1` si no se establece. Auméntala al rotar la clave |
| `QUIRE_MASTER_KEY_RETIRED` | Claves maestras anteriores que aún se necesitan para leer lo que cifraron, en el formato `v1=<base64>`. Elimínalas cuando termine la rotación y no quede nada pendiente |
| `QUIRE_COLLAB_SIGNING_KEY` | Clave compartida por web y collab para firmar tokens de edición |
| `QUIRE_BACKUP_SIGNING_KEY` | Firma las copias de seguridad de cursos (opcional) |

Guarda una copia de `QUIRE_MASTER_KEY` fuera de este host. Si restauras una base de datos sin ella, no podrás descifrar las credenciales almacenadas.

### Base de datos <!--quire:database-->

| Parámetro | Significado |
| --- | --- |
| `POSTGRES_PASSWORD` | Contraseña del superusuario, utilizada por el contenedor y las copias de seguridad |
| `QUIRE_DB_APP_PASSWORD`, `QUIRE_DB_MIGRATOR_PASSWORD`, `QUIRE_DB_REPORT_PASSWORD`, `QUIRE_DB_AUDIT_PASSWORD` | Contraseñas de roles, establecidas en el primer inicio |
| `DATABASE_URL` | Rol de la aplicación. Todas sus consultas están sujetas a seguridad a nivel de fila |
| `DATABASE_MIGRATOR_URL`, `QUIRE_MIGRATION_URL` | Rol de migración, para `migrate` e `init` |
| `QUIRE_SUPERUSER_URL` | Solo se usa en la primera ejecución |
| `QUIRE_REPORT_DATABASE_URL` | Rol de informes de solo lectura, para los informes y su generador |
| `QUIRE_AUDIT_DATABASE_URL` | Rol de auditoría, para la consola de auditoría y la exportación SIEM |
| `QUIRE_DATABASE_ID` | Cualquier UUID, fijo durante toda la vida de la instalación |

Las contraseñas de los roles solo se aplican cuando se crea el volumen de la base de datos por primera vez. Para cambiar una más adelante, usa `ALTER ROLE` y actualiza la URL correspondiente.

`QUIRE_REPORT_DATABASE_URL` se usa para la base de datos física configurada en `DATABASE_URL`. Para cualquier otra base de datos física registrada, configura su propia URL de conexión `quire_report` en los entornos de web y del worker y, en el campo **Variable de entorno de informes** de esa base de datos, introduce el nombre de la variable con el formato `env:NAME`. La referencia debe apuntar a la misma base de datos que la conexión de la aplicación, idealmente a su réplica de lectura. Todas las superficies de informes siguen la conexión de informes propia de la base de datos del tenant: el generador y los informes guardados, las entregas programadas, las exportaciones de informes, las analíticas, el registro de auditoría, los recursos de auditoría REST y la búsqueda de auditoría del asistente. Ninguna toma prestada la URL de informes de otra base de datos. Si una base de datos no tiene conexión para informes, los informes normales se ejecutan con su propia conexión de aplicación, pero las analíticas y todas las consultas de auditoría se rechazan y lo indican, porque el rol de la aplicación no puede leer el registro de auditoría.

### Controladores <!--quire:drivers-->

| Parámetro | Valores de esta versión | Notas |
| --- | --- | --- |
| `QUIRE_STORAGE_DRIVER` | `local` (predeterminado), `s3` o `azure` | `local` guarda archivos en el volumen `files`. `s3` admite AWS S3, interoperabilidad con R2 y GCS, y otros almacenes compatibles con S3, con cargas reanudables de varias partes |
| `QUIRE_REALTIME_DRIVER` | `inprocess` (predeterminado), `sse`, `centrifugo` o `durable_objects` | `inprocess` sirve para un único contenedor web; usa `centrifugo` o `sse` cuando tengas varios |
| `QUIRE_CACHE_DRIVER` | `memory` (predeterminado), `postgres` o `valkey` | `memory` es por proceso; usa `valkey` o `postgres` para que los límites de solicitudes se apliquen en todos los contenedores |
| `QUIRE_VIDEO_DRIVER` | `ffmpeg` (predeterminado) o `progressive_mp4` | O un proveedor alojado: Cloudflare Stream, Mux o Bunny, mediante sus claves |
| `QUIRE_IMAGE_DRIVER` | `noop` (predeterminado), `imgproxy` o `cloudflare` | `noop` sirve todas las imágenes con su tamaño original. `imgproxy` necesita el perfil `images` y los parámetros siguientes; `cloudflare` usa Cloudflare Images |
| `QUIRE_MEETING_PROVIDER` | `bbb`, `zoom`, `teams`, `meet`, `jitsi` o `in_process` | Proveedor predeterminado de la plataforma para sesiones en directo. Si no se configura, las sesiones en directo indican que no están configuradas hasta que una organización conecte su propia cuenta en Integraciones, Proveedor de sesiones en directo. La cuenta propia de la organización siempre prevalece sobre este valor. Los ajustes de cada proveedor (`BBB_URL` y `BBB_SECRET`, las variables `ZOOM_*`, `TEAMS_*`, `GOOGLE_MEET_*` y `JITSI_*`) solo se leen para el proveedor indicado aquí |
| `QUIRE_MEETING_REGIONS` | Lista de `eu`, `uk`, `us` separada por comas | Regiones donde el proveedor predeterminado de la plataforma procesa las reuniones. Si no se configura, no se comprueba frente a una organización fijada a una región, igual que antes. La organización indica las regiones de su propia cuenta en la página correspondiente |

Si esta versión no incluye el valor de un controlador, la capa web se niega a iniciarse e indica el parámetro, en lugar de sustituirlo silenciosamente por el valor predeterminado.

### Imágenes <!--quire:images-->

Las páginas solicitan imágenes en cuatro tamaños fijos mediante `/api/files/{id}/image/{size}`. La ruta comprueba los mismos permisos de acceso que el archivo y después redirige al servicio de imágenes. Cada organización puede solicitar `QUIRE_IMAGE_SPECS_PER_HOUR` (2000 de forma predeterminada) pares nuevos de imagen y tamaño por hora; los tamaños que ya se hayan generado esa hora no cuentan. Si tienes más de un contenedor web, usa `valkey` o `postgres` para `QUIRE_CACHE_DRIVER` para que el límite se aplique en todos ellos.

| Parámetro | Controlador | Notas |
| --- | --- | --- |
| `IMGPROXY_URL` | `imgproxy` | Dirección a la que acceden los navegadores para llegar a imgproxy; por ejemplo, `https://images.example.org`. El perfil `images` lo publica en `QUIRE_IMAGES_PORT` (8082) |
| `IMGPROXY_KEY`, `IMGPROXY_SALT` | `imgproxy` | Cadenas hexadecimales con los mismos valores que se pasan al iniciar imgproxy. Genera cada una con `openssl rand -hex 32`. Quire firma todas las direcciones de imágenes con ellas, así que imgproxy solo procesa lo que Quire solicita |
| `QUIRE_IMAGE_SOURCE_ORIGIN` | `imgproxy` con almacenamiento local | Origen desde el que imgproxy obtiene los originales. Compose establece `http://web:3000`. Con almacenamiento `s3` o `azure`, imgproxy obtiene los archivos del bucket y no usa este valor |
| `CLOUDFLARE_ACCOUNT_ID`, `CLOUDFLARE_IMAGES_TOKEN`, `CLOUDFLARE_IMAGES_ACCOUNT_HASH` | `cloudflare` | Un token de API con permiso para editar Images y el hash de cuenta de Images, en Developer resources. Activa las variantes flexibles para la cuenta |
| `CLOUDFLARE_IMAGES_SIGNING_KEY` | `cloudflare` | Opcional. Si se establece, las imágenes son privadas y todas las direcciones están firmadas y caducan. Sin ella, las imágenes son públicas en direcciones derivadas de `QUIRE_SECRET_KEY` que nadie puede adivinar |

Cloudflare Images conserva su propia copia de cada original que sirve. Cuando se elimina un archivo, el worker borra esa copia antes que el original.

### Cola <!--quire:queue-->

Los trabajos en segundo plano usan pg-boss en la misma base de datos de Postgres, por lo que no hace falta ejecutar ni configurar un servicio de cola. Los trabajos se ponen en cola en la misma transacción que el cambio que los generó, así que un fallo no puede perder un trabajo ni enviarlo dos veces. `QUIRE_QUEUE_DRIVER` es `pgboss` aquí, su valor predeterminado; `vercel` y `cloudflare` trasladan solo las entregas ligeras de notificaciones y webhooks a la cola propia de la plataforma. Las guías de Vercel y Cloudflare describen esas colas y cómo las capas web añaden elementos.

### Correo electrónico <!--quire:email-->

Configura una de estas opciones:

- `QUIRE_EMAIL_PROVIDER_CONFIG`: un objeto JSON que indica un proveedor HTTP y sus credenciales, como `{"provider":"postmark","token":"..."}`. Se admiten Postmark, Amazon SES, Mailgun, SendGrid y Resend.
- `QUIRE_SMTP_URL`: `smtp://user:password@host:587`. Solo para este destino; los destinos sin servidor bloquean SMTP.

`QUIRE_MAIL_FROM` es el remitente. Para probar Quire, inicia el perfil `devmail`, establece `QUIRE_SMTP_URL=smtp://mailpit:1025` y consulta el correo en `http://localhost:8025`.

### Servicios opcionales <!--quire:optional-services-->

| Parámetro | Con el perfil |
| --- | --- |
| `CLAMAV_URL=tcp://clamav:3310` | `scan` |
| `GOTENBERG_URL=http://gotenberg:3000` | `preview` |
| `IMGPROXY_KEY`, `IMGPROXY_SALT` | `images` |
| `VALKEY_URL=redis://valkey:6379` | `cache` |
| `QUIRE_OPENSEARCH_URL` o `QUIRE_MEILISEARCH_URL` | Búsqueda externa; de lo contrario, búsqueda de texto completo de Postgres |
| `QUIRE_BREACH_CHECK_PROVIDER=off`, `QUIRE_BREACH_CHECK_URL` | Comprobación de contraseñas filtradas. Está activada de forma predeterminada y consulta `api.pwnedpasswords.com` (solo se envía un prefijo de cinco caracteres del hash); `off` la desactiva y la URL apunta a una API de rangos que alojes tú |

### Observabilidad <!--quire:observability-->

`OTEL_EXPORTER_OTLP_ENDPOINT` indica el colector al que todos los procesos envían trazas y métricas; con el perfil `observability` es `http://otelcol:4318`, y `docker/otel-collector.yaml` es donde se añade el exportador de tu backend. Cuando está configurado, la capa web, el worker, el scheduler, content y collab exportan spans mediante OTLP/HTTP (solicitudes web, transacciones con bases de datos de tenants, trabajos del worker y llamadas salientes), así como métricas al mismo endpoint cada minuto (`OTEL_METRICS_EXPORTER=none` las desactiva). `OTEL_TRACES_SAMPLER_ARG` establece la proporción de trazas que se conserva. Los registros se envían a la salida estándar con el nivel `LOG_LEVEL`, y Compose los rota. Las trazas nunca contienen datos personales.

### Salida regional (residencia de datos en la UE) <!--quire:regional-egress-eu-data-residency-->

`QUIRE_REGION=eu` indica que la pila presta servicio a organizaciones de la Unión Europea. En ese caso, el worker limita las solicitudes salientes que se hacen en nombre de una organización fijada a la UE a una lista de permitidos (sección 8.1 de 21-compliance.md). La lista incluye los hosts que los servicios configurados declaran para la región (el endpoint de almacenamiento, el proveedor de correo, el proveedor de vídeo alojado, los destinos de almacenamiento propios de la organización, los proveedores de IA y la cuenta de correo electrónico), los hosts de cualquier servicio cubierto por una excepción vigente y los que incluyas en `QUIRE_EGRESS_ALLOW_HOSTS`. Las solicitudes a cualquier otro host público se rechazan antes de enviarse, el rechazo se registra en el historial de auditoría de la organización como `privacy/egress_refused` y aparece en Cumplimiento, Residencia de datos.

| Parámetro | Valores | Efecto |
| --- | --- | --- |
| `QUIRE_EGRESS_ALLOW_HOSTS` | Lista de nombres de host separados por comas o `*.example.org` para todos los subdominios | Hosts adicionales a los que puede acceder una organización de la UE. Aquí deben incluirse los endpoints de webhook, xAPI y SIEM, los feeds de blogs y los hosts de Amazon SES, porque son opciones propias de la organización y ningún servicio los declara. Las direcciones de bucle local, privadas y los nombres de una sola etiqueta, como `web` o `clamav`, pertenecen a tu red y nunca se comprueban |

Las organizaciones del Reino Unido y de Estados Unidos no están sujetas a una lista de hosts; solo se les aplican las comprobaciones de región del servicio. Configura la lista en el worker; la página de administración la lee en la capa web para mostrar la lista de permitidos, así que inclúyela en `docker/.env`, que leen todos los servicios.

La comprobación de la aplicación da un error claro y genera una entrada de auditoría, pero no es la garantía: el código puede contener errores. La garantía la proporciona la red. Compose no la impone por sí mismo. Para una pila regional, coloca los servicios `worker` y `web` en una red `internal: true` cuya única ruta de salida pase por un proxy de salida (por ejemplo, un contenedor Squid o tinyproxy) que permita los mismos hosts que `QUIRE_EGRESS_ALLOW_HOSTS`, además de los hosts de tus servicios configurados, y establece `HTTPS_PROXY` para esos servicios. La página de residencia enumera los hosts exactos que permite la aplicación, de modo que puedes comparar ambas listas.

## Estado de salud <!--quire:health-->

| Endpoint | Significado |
| --- | --- |
| `/healthz` | Disponibilidad: el proceso responde. Las comprobaciones de Compose usan este endpoint |
| `/readyz` | Preparación: las dependencias son accesibles y cada servicio opcional indica si está configurado. Indica este endpoint a tu balanceador de carga |

`docker compose -f docker/compose.yaml ps` muestra el estado de cada servicio.

## TLS <!--quire:tls-->

El servicio `proxy` (Caddy, Apache-2.0, `docker/caddy/Caddyfile`) forma parte de la pila predeterminada. Responde en los puertos 80 y 443 y enruta:

| Host o ruta | Destino |
| --- | --- |
| `QUIRE_PROXY_CONTENT_HOST` | `content` |
| `QUIRE_PROXY_APP_HOST`, cada subdominio de tenant y dominio personalizado | `web` |
| `/_collab/` en esos hosts | `collab` (WebSocket, `QUIRE_COLLAB_URL`) |
| `/_realtime/connection/` en esos hosts | WebSocket de cliente de `centrifugo`; su API de servidor nunca se expone |
| `/_images/` en esos hosts | `imgproxy`, con el perfil `images` (`IMGPROXY_URL`) |

`init-env.sh` deriva `QUIRE_PROXY_APP_HOST`, `QUIRE_PROXY_CONTENT_HOST`, `QUIRE_PROXY_HTTPS_PORT`, `QUIRE_COLLAB_URL` e `IMGPROXY_URL` de los dos orígenes, para que no haya discrepancias entre ellos. Si cambias un origen manualmente, modifica también esos valores.

Los certificados dependen de `QUIRE_PROXY_TLS`:

- `internal` (predeterminado): autoridad de certificación propia de Caddy para `localhost`, `*.localhost` y `lvh.me`. Confía en su certificado raíz una vez y luego navega:

  ```sh
  docker compose -f docker/compose.yaml cp \
    proxy:/data/caddy/pki/authorities/local/root.crt ./quire-local-ca.crt
  ```

  Añade `quire-local-ca.crt` al almacén de confianza del sistema o del navegador. En `curl`, pásalo con `--cacert`.
- Una dirección de correo electrónico: certificados ACME automáticos (Let's Encrypt y luego ZeroSSL) para nombres de host reales. El DNS de ambos orígenes y de cada host de tenant debe apuntar aquí, y los puertos 80 y 443 deben ser accesibles desde Internet.

Los hosts de tenants reciben certificados cuando se visitan por primera vez, y solo si web confirma que el nombre pertenece a esta instalación (`/tls-allowed`, mediante la red de Compose). No hace falta un certificado comodín ni un complemento del proveedor DNS; alguien ajeno que dirija un nombre a este host no puede provocar una solicitud de certificado. Los certificados y la autoridad local se guardan en el volumen `caddy-data`; si usas `internal`, inclúyelo en las demás copias de seguridad.

Web solo confía en `X-Forwarded-For` cuando lo envía el proxy: este tiene una dirección fija (`QUIRE_PROXY_ADDRESS`, `172.29.64.10` de forma predeterminada) en una subred fija (`QUIRE_COMPOSE_SUBNET`), y `QUIRE_TRUSTED_PROXY_CIDRS` contiene esa dirección. Si la subred coincide con una red del host, cambia ambas y ejecuta `docker compose down` antes de `up`.

## Detrás de tu propio proxy inverso <!--quire:behind-your-own-reverse-proxy-->

Para usar el balanceador o proxy que ya tengas en lugar de este, deja fuera `proxy` (`docker compose up -d --scale proxy=0`) y termina TLS delante de `web` (8080), `content` (8081), `collab` (1234, WebSocket) y `centrifugo` (8000, WebSocket). Establece las direcciones públicas en `QUIRE_APP_ORIGIN`, `QUIRE_CONTENT_ORIGIN` y `QUIRE_COLLAB_URL` (`wss://`), y el rango de direcciones de tu proxy en `QUIRE_TRUSTED_PROXY_CIDRS`.

## Solución de problemas <!--quire:troubleshooting-->

- `init` termina con «QUIRE_DATABASE_ID is not a UUID»: establece el valor con `uuidgen`.
- `web` se reinicia con «did not start on compose»: el registro enumera cada parámetro que no puede usar y qué valor debes emplear.
- Cambiar una contraseña de rol en `.env` después del primer inicio no tiene efecto: el script de inicialización solo se ejecuta una vez. Usa `ALTER ROLE`.
- Las cargas fallan con un error de análisis mientras está configurado `CLAMAV_URL`: ClamAV descarga sus firmas en el primer inicio, lo que tarda unos minutos.

Source: https://docs.quirelms.com/es/ops/install/index.mdx
