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, rolNOLOGINcon permisos mínimos y RLS.commerce_connect_db, session store aislado de TimeLiber Connect;commerce_connect_ownerpara Prisma Migrate ycommerce_connect_runtimepara 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:
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. Unshopify app deploya secas apunta a la app DEV. SHOPIFY_DISTRIBUTIONes obligatoria y el conector se niega a arrancar sin ella.publicen producción,single-merchanten DEV. Un valor equivocado no da error: App Bridge deja de adjuntar el ID token y la vinculación falla como un302mudo.SHOPIFY_API_KEYya no tiene valor por defecto en el Compose. Levantar sin--env-file .env.prodfalla de inmediato en vez de arrancar la app Public con la identidad de la app DEV.- DEV nunca apunta su
DATABASE_URLacommerce_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.