Tema
Despliegue con Docker
Despliega Capyshop en produccion usando Docker Compose con la imagen de contenedor preconstruida y PostgreSQL con pgvector.
Requisitos Previos
- Docker Engine 20+
- Docker Compose v2+
1. Crear un Archivo Docker Compose
Crea un docker-compose.yml en tu directorio de despliegue:
yaml
services:
postgres:
image: pgvector/pgvector:pg17
restart: always
environment:
POSTGRES_USER: capyshop
POSTGRES_PASSWORD: changeme
POSTGRES_DB: capyshop
volumes:
- postgres_data:/var/lib/postgresql/data
app:
image: capyshop/capyshop:latest
restart: unless-stopped
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://capyshop:changeme@postgres:5432/capyshop
- BETTER_AUTH_SECRET=<generate-with-openssl-rand-base64-32>
- MASTER_SECRET=<generate-with-openssl-rand-base64-32>
- TRUSTED_ORIGINS=https://mystore.com
- BASE_URL=https://mystore.com
volumes:
- ./data:/app/data
depends_on:
- postgres
volumes:
postgres_data:Reemplaza los valores de ejemplo:
| Variable | Descripcion |
|---|---|
BETTER_AUTH_SECRET | Clave secreta para la firma de sesiones. Genera una con openssl rand -base64 32. |
MASTER_SECRET | Secreto maestro de la aplicacion para cifrado. Genera uno con openssl rand -base64 32. |
POSTGRES_USER | Nombre de usuario de PostgreSQL (debe coincidir en postgres y DATABASE_URL). |
POSTGRES_PASSWORD | Contrasena de PostgreSQL (debe coincidir en postgres y DATABASE_URL). |
POSTGRES_DB | Nombre de la base de datos de PostgreSQL (debe coincidir en postgres y DATABASE_URL). |
TRUSTED_ORIGINS | Lista de origenes confiables separados por comas para proteccion CSRF y redirecciones OAuth (ej., https://mitienda.com). |
BASE_URL | URL publica de la tienda, usada en correos, SEO, sitemaps (ej., https://mitienda.com). |
Opcional: Configuracion de SMTP por Variables de Entorno
Por defecto, las credenciales SMTP se gestionan en Configuracion → Email del panel de administracion. Si prefieres configurar el SMTP en el momento del despliegue, puedes definir las siguientes variables de entorno. Cuando estan definidas, sobreescriben los valores del panel y los campos correspondientes en el formulario de Email se deshabilitan.
yaml
environment:
# ...otras variables
- SMTP_HOST=smtp.sendgrid.net
- SMTP_PORT=587
- SMTP_USER=apikey
- SMTP_PASSWORD=tu-contrasena-smtp| Variable | Descripcion |
|---|---|
SMTP_HOST | Hostname del servidor de correo (ej., smtp.sendgrid.net). |
SMTP_PORT | Puerto del servidor. Generalmente 587 para TLS o 465 para SSL. Debe ser entero en [1, 65535]. |
SMTP_USER | Nombre de usuario de inicio de sesion SMTP. |
SMTP_PASSWORD | Contrasena de inicio de sesion SMTP. Almacenada en texto plano en el entorno — no pasa por la capa de cifrado de la base de datos. |
Cada variable es independiente. Si solo se define SMTP_HOST, los otros tres campos SMTP siguen siendo editables en la interfaz administrativa y se leen de la base de datos.
Opcional: Almacenamiento de Assets en S3
Por defecto, las imagenes subidas y otros assets se almacenan en disco local en data/files/. Para enviar las cargas a un bucket compatible con S3 y servirlas desde un CDN, define el modo de almacenamiento como s3 y configura las credenciales del bucket.
yaml
environment:
# ...otras vars
- ASSETS_STORAGE_MODE=s3
- ASSETS_S3_ENDPOINT=https://s3.amazonaws.com
- ASSETS_S3_REGION=us-east-1
- ASSETS_S3_BUCKET=my-store-assets
- ASSETS_S3_ACCESS_KEY_ID=AKIA...
- ASSETS_S3_SECRET_ACCESS_KEY=...
- ASSETS_PUBLIC_BASE_URL=https://cdn.mystore.com
- ASSETS_MAX_BYTES=10gb| Variable | Descripcion |
|---|---|
ASSETS_STORAGE_MODE | local (predeterminado) o s3. Cuando es s3, las seis vars ASSETS_S3_* y ASSETS_PUBLIC_BASE_URL son obligatorias. |
ASSETS_S3_ENDPOINT | Endpoint de la API S3 (ej., https://s3.amazonaws.com, o la URL de tu proveedor para Cloudflare R2, Backblaze B2, MinIO, etc.). |
ASSETS_S3_REGION | Region del bucket (ej., us-east-1, auto para R2). |
ASSETS_S3_BUCKET | Nombre del bucket. |
ASSETS_S3_ACCESS_KEY_ID | Access key con permisos de lectura/escritura en el bucket. |
ASSETS_S3_SECRET_ACCESS_KEY | Secret access key. |
ASSETS_PUBLIC_BASE_URL | URL publica base que la tienda utiliza para cargar los assets (ej., tu dominio CDN apuntando al bucket). |
ASSETS_MAX_BYTES | Limite cumulativo opcional de almacenamiento entre todos los archivos. Acepta 10gb, 500mb o un recuento de bytes en bruto. Aplica en modo local y s3. Los limites por carga (5 MB imagen / 50 MB video) permanecen sin cambios. |
Al migrar una tienda existente de local a s3, ejecuta el script de migracion incluido una vez dentro de un contenedor desplegado para subir los archivos existentes al bucket y pre-generar las variantes WebP:
bash
docker exec -it <container> node build/scripts/migrate-assets-to-s3.mjsEl script es idempotente — volver a ejecutarlo es seguro e ignora el trabajo que ya esta hecho. Si tus originales existentes residen en el host (por ejemplo, /docker/<store>/app_data/files/), monta esa ruta en /app/data/files dentro del contenedor para que el script pueda recogerlos; de lo contrario, recurrira a lo que ya este en el bucket.
2. Iniciar la Aplicacion
bash
docker compose up -dEsto inicia dos servicios:
- postgres — PostgreSQL 17 con pgvector (
pgvector/pgvector:pg17), escuchando en el puerto5432. - app — El contenedor de la aplicacion (
capyshop/capyshop:latest), expuesto en el puerto3000.
Al iniciar, el contenedor de la aplicacion ejecuta automaticamente las migraciones de base de datos (prisma migrate deploy) antes de iniciar el servidor.
3. Verificar
bash
# Verificar que ambos contenedores esten corriendo
docker compose ps
# Revisar los logs de la aplicacion
docker compose logs appLa aplicacion deberia ser accesible en http://<tu-host>:3000.
Proxy Inverso
Para despliegues en produccion con HTTPS, coloca un proxy inverso (como Traefik, Caddy o nginx) delante de la aplicacion. El proxy inverso se encarga de la terminacion TLS y reenvía el trafico al puerto 3000.
Habilitando Compresion Gzip con Traefik
Si utilizas Traefik como proxy inverso, habilita la compresion gzip para reducir el tamano de las respuestas y mejorar la velocidad de carga — un factor en el posicionamiento en motores de busqueda. Agrega las siguientes labels al servicio app en tu docker-compose.yml:
yaml
labels:
- "traefik.http.middlewares.compress.compress.minresponsebodybytes=256"
- "traefik.http.routers.STORE_NAME-app.middlewares=compress"Reemplaza STORE_NAME con el nombre del router Traefik de tu tienda. La primera label define un middleware de compresion que aplica gzip a todas las respuestas mayores a 256 bytes. La segunda label asocia ese middleware al router de tu tienda.
Volumenes
| Volumen / Montaje | Proposito |
|---|---|
postgres_data | Persiste los datos de PostgreSQL entre reinicios de contenedores. |
./data:/app/data | Persiste archivos subidos y datos de la aplicacion entre reinicios. |
Compilar desde el Codigo Fuente
Si prefieres compilar la imagen tu mismo en lugar de usar la preconstruida:
bash
docker build -t capyshop .El Dockerfile usa una compilacion multi-etapa:
- Instala todas las dependencias y compila la aplicacion.
- Copia solo las dependencias de produccion y el resultado de la compilacion a la imagen final.
- Ejecuta
prisma migrate deployy luego inicia el servidor en el puerto3000.
Para usar tu imagen personalizada, actualiza el campo image en tu docker-compose.yml, o ejecuta de forma independiente:
bash
docker run -p 3000:3000 \
-e DATABASE_URL=postgresql://user:pass@host:5432/db \
-e BETTER_AUTH_SECRET=your-secret \
-e MASTER_SECRET=your-secret \
capyshopSolucion de Problemas
| Error | Causa | Solucion |
|---|---|---|
app se cierra inmediatamente | Variables de entorno faltantes o invalidas | Revisa docker compose logs app y verifica todas las variables de entorno requeridas |
ECONNREFUSED hacia postgres | La aplicacion inicio antes de que la base de datos estuviera lista | Reinicia la aplicacion: docker compose restart app |
P3009 - failed migrations | Una migracion anterior dejo un estado inconsistente | DROP TABLE IF EXISTS _prisma_migrations CASCADE; en la base de datos, luego reinicia |
type "vector" does not exist | La extension pgvector no fue creada | La imagen pgvector/pgvector:pg17 la incluye, pero ejecuta CREATE EXTENSION IF NOT EXISTS vector; si usas otra imagen |
Puerto 3000 ya en uso | Otro proceso esta usando el puerto | Detiene el proceso en conflicto o cambia el mapeo de puertos en docker-compose.yml |
| Archivos subidos perdidos tras reinicio | Volumen ./data no montado | Asegurate de que la seccion volumes incluya ./data:/app/data |