Skip to content

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:

VariableDescripcion
BETTER_AUTH_SECRETClave secreta para la firma de sesiones. Genera una con openssl rand -base64 32.
MASTER_SECRETSecreto maestro de la aplicacion para cifrado. Genera uno con openssl rand -base64 32.
POSTGRES_USERNombre de usuario de PostgreSQL (debe coincidir en postgres y DATABASE_URL).
POSTGRES_PASSWORDContrasena de PostgreSQL (debe coincidir en postgres y DATABASE_URL).
POSTGRES_DBNombre de la base de datos de PostgreSQL (debe coincidir en postgres y DATABASE_URL).
TRUSTED_ORIGINSLista de origenes confiables separados por comas para proteccion CSRF y redirecciones OAuth (ej., https://mitienda.com).
BASE_URLURL 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
VariableDescripcion
SMTP_HOSTHostname del servidor de correo (ej., smtp.sendgrid.net).
SMTP_PORTPuerto del servidor. Generalmente 587 para TLS o 465 para SSL. Debe ser entero en [1, 65535].
SMTP_USERNombre de usuario de inicio de sesion SMTP.
SMTP_PASSWORDContrasena 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
VariableDescripcion
ASSETS_STORAGE_MODElocal (predeterminado) o s3. Cuando es s3, las seis vars ASSETS_S3_* y ASSETS_PUBLIC_BASE_URL son obligatorias.
ASSETS_S3_ENDPOINTEndpoint de la API S3 (ej., https://s3.amazonaws.com, o la URL de tu proveedor para Cloudflare R2, Backblaze B2, MinIO, etc.).
ASSETS_S3_REGIONRegion del bucket (ej., us-east-1, auto para R2).
ASSETS_S3_BUCKETNombre del bucket.
ASSETS_S3_ACCESS_KEY_IDAccess key con permisos de lectura/escritura en el bucket.
ASSETS_S3_SECRET_ACCESS_KEYSecret access key.
ASSETS_PUBLIC_BASE_URLURL publica base que la tienda utiliza para cargar los assets (ej., tu dominio CDN apuntando al bucket).
ASSETS_MAX_BYTESLimite 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.mjs

El 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 -d

Esto inicia dos servicios:

  • postgres — PostgreSQL 17 con pgvector (pgvector/pgvector:pg17), escuchando en el puerto 5432.
  • app — El contenedor de la aplicacion (capyshop/capyshop:latest), expuesto en el puerto 3000.

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 app

La 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 / MontajeProposito
postgres_dataPersiste los datos de PostgreSQL entre reinicios de contenedores.
./data:/app/dataPersiste 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:

  1. Instala todas las dependencias y compila la aplicacion.
  2. Copia solo las dependencias de produccion y el resultado de la compilacion a la imagen final.
  3. Ejecuta prisma migrate deploy y luego inicia el servidor en el puerto 3000.

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 \
  capyshop

Solucion de Problemas

ErrorCausaSolucion
app se cierra inmediatamenteVariables de entorno faltantes o invalidasRevisa docker compose logs app y verifica todas las variables de entorno requeridas
ECONNREFUSED hacia postgresLa aplicacion inicio antes de que la base de datos estuviera listaReinicia la aplicacion: docker compose restart app
P3009 - failed migrationsUna migracion anterior dejo un estado inconsistenteDROP TABLE IF EXISTS _prisma_migrations CASCADE; en la base de datos, luego reinicia
type "vector" does not existLa extension pgvector no fue creadaLa imagen pgvector/pgvector:pg17 la incluye, pero ejecuta CREATE EXTENSION IF NOT EXISTS vector; si usas otra imagen
Puerto 3000 ya en usoOtro proceso esta usando el puertoDetiene el proceso en conflicto o cambia el mapeo de puertos en docker-compose.yml
Archivos subidos perdidos tras reinicioVolumen ./data no montadoAsegurate de que la seccion volumes incluya ./data:/app/data