Saltar a contenido

Desplegar Commerce sobre la infraestructura compartida

Commerce usa los contenedores existentes timeliber-postgres y timeliber-redis. No se crea otro motor de datos.

Volumen PostgreSQL nuevo

infra/postgres/init/12_commerce.sh crea automáticamente:

  • commerce_db;
  • commerce_owner, usado sólo por Alembic;
  • commerce_runtime, usado por la API;
  • commerce_app, rol NOLOGIN con permisos mínimos y RLS.
  • commerce_connect_db, session store aislado de TimeLiber Connect;
  • commerce_connect_owner para Prisma Migrate y commerce_connect_runtime para el proceso del conector.

Los passwords se leen desde Docker secrets. El script falla de forma explícita si faltan, en vez de crear una credencial vacía.

Volumen PostgreSQL existente

Los scripts de docker-entrypoint-initdb.d no vuelven a ejecutarse. El flujo repetible es:

sudo bash infra/scripts/bootstrap-commerce-secrets.sh
sudo bash infra/scripts/bootstrap-commerce-connector-db-secrets.sh
sudo bash infra/scripts/bootstrap-commerce-shopify-runtime-secrets.sh
sudo bash infra/scripts/provision-commerce-existing-postgres.sh
docker compose -f apps/commerce/docker-compose.yml build commerce-api commerce-web commerce-shopify-connect
docker compose --profile operations -f apps/commerce/docker-compose.yml run --rm commerce-migrate
docker compose --profile operations -f apps/commerce/docker-compose.yml run --rm commerce-shopify-connect-migrate
docker compose -f apps/commerce/docker-compose.yml up -d commerce-api commerce-web

El bootstrap se niega a sobrescribir secretos existentes. Los archivos quedan root:1000 con modo 640: el runtime no-root puede leerlos, pero otros usuarios del host no. El provisioner sólo crea los roles y la base cuando faltan; no elimina ni modifica bases de otras verticales.

El último up omite deliberadamente commerce-shopify-connect. Se inicia sólo después de rotar el secreto Shopify expuesto, montar el valor nuevo fuera del repositorio y completar la prueba DEV. Las migraciones de su session store sí pueden prepararse antes porque no requieren la credencial Shopify.

Rotar e instalar la credencial Shopify

Primero rota el client secret en Shopify Dev Dashboard. No lo pegues en chat, .env, comandos ni argumentos. Desde una terminal interactiva del servidor:

sudo bash infra/scripts/install-rotated-shopify-api-secret.sh
sudo bash infra/scripts/preflight-commerce-shopify-connect.sh

El instalador pide el valor dos veces sin mostrarlo, lo escribe atómicamente como root:1000 modo 640 y genera una atestación local no secreta. El preflight verifica existencia, permisos, tamaños, formato de la clave de sesión y coherencia de la rotación sin imprimir valores. Sólo después de que pase:

docker compose -f apps/commerce/docker-compose.yml up -d commerce-shopify-connect

No usar docker compose up sin nombres de servicio: podría iniciar el conector antes de cumplir este gate.

Verificación

docker compose -f apps/commerce/docker-compose.yml ps
curl -fsS https://commerce.timeliber.com.co/api/v1/health/ready
curl -fsS https://commerce.timeliber.com.co/register
curl -fsS https://commerce.timeliber.com.co/shopify/health

El último endpoint debe responder JSON con service=timeliber-commerce-shopify-connect; un 200 con HTML del frontend no demuestra que el conector esté activo. El status ready implica además que el proceso pudo ejecutar una consulta mínima contra commerce_connect_db.

Después debe recorrerse desde el navegador: registro, login automático, creación de empresa, recarga de sesión y logout. El conector Shopify permanece apagado hasta rotar y montar su secreto independiente.

Separación DEV / PROD de la app Shopify

Este repositorio contiene dos aplicaciones Shopify distintas que comparten el mismo código del conector. No comparten nada más.

DEV PRODUCCIÓN
App TimeLiber Connect TimeLiber Commerce
Distribución Custom Public
Client ID 601ed316… b79f8d7f…
Configuración shopify.app.toml shopify.app.production.toml
URL túnel del CLI (.invalid como marcador) https://commerce.timeliber.com.co
Sesiones base propia de desarrollo commerce_connect_db
Secreto el que entrega shopify app dev /etc/timeliber/secrets/commerce/shopify_api_secret

Por qué importa

Hasta el 2026-09-02 la app DEV declaraba ante Shopify la URL de producción y sus mismos endpoints de webhook. Como los webhooks DEV van firmados con el secreto DEV y el runtime desplegado valida con el secreto Public, esas entregas fallaban HMAC. Además, una instalación DEV y una Public sobre la misma tienda colisionan en la fila offline_<shop> del session store compartido.

Reglas operativas

  • Producción se libera con shopify app deploy --config production. Un shopify app deploy a secas apunta a la app DEV.
  • SHOPIFY_DISTRIBUTION es obligatoria y el conector se niega a arrancar sin ella. public en producción, single-merchant en DEV. Un valor equivocado no da error: App Bridge deja de adjuntar el ID token y la vinculación falla como un 302 mudo.
  • SHOPIFY_API_KEY ya no tiene valor por defecto en el Compose. Levantar sin --env-file .env.prod falla de inmediato en vez de arrancar la app Public con la identidad de la app DEV.
  • DEV nunca apunta su DATABASE_URL a commerce_connect_db.

Cambiar el frontend al enlace público

VITE_SHOPIFY_PUBLIC_INSTALL_URL se inlinea en el bundle en tiempo de build, así que exige reconstruir, no reiniciar:

# con el enlace oficial ya en .env.prod
docker compose --env-file .env.prod -f apps/commerce/docker-compose.yml \
  build --no-cache commerce-web
docker compose --env-file .env.prod -f apps/commerce/docker-compose.yml \
  up -d commerce-web

Vacío mantiene el flujo DEV de código de conexión. Un valor https:// cambia el dashboard al enlace oficial de instalación de Shopify.

Verificar qué app está sirviendo

docker inspect timeliber-commerce-shopify-connect \
  --format '{{range .Config.Env}}{{println .}}{{end}}' \
  | grep -E 'SHOPIFY_API_KEY|SHOPIFY_DISTRIBUTION'

Debe mostrar el client ID de la app Public y public. El client ID no es un secreto; el secreto nunca se imprime.