Instalación
Hay dos formas de levantar el sistema, y las dos acaban en el mismo sitio: el asistente del navegador. Esa es la idea de fondo — arrancar y configurar son cosas distintas. Arrancar puede hacerse con Docker, con npm o con un botón de la nube; configurar se hace siempre igual, y se hace sin terminal.
Con Docker — recomendada
Sección titulada «Con Docker — recomendada»docker compose -f docker-compose.full.yml up -dY ya. Levanta cuatro piezas: Postgres, el backend, el panel y la tienda. (Redis ya no hace falta: la cola de avisos, los flujos y las tareas viven en Postgres.)
- Tienda → http://localhost:3000
- Panel → http://localhost:7001/admin
La primera vez tarda unos minutos, porque construye las tres imágenes. Después
arranca en unos diez segundos: el contenedor del backend crea el esquema si la
base está vacía —o aplica lo que falte si no—, prepara la tienda y arranca. Es
lo mismo que hace preparar a mano (más abajo), y se repite sin miedo en cada
arranque. Al abrir el panel te recibe
el asistente: cuatro comprobaciones, el nombre de la tienda, tu cuenta y, si
quieres, un catálogo de ejemplo para ver el circuito completo funcionando.
Para seguir el arranque:
docker compose -f docker-compose.full.yml logs -f backendPublicarlo de verdad
Sección titulada «Publicarlo de verdad»Copia .env.docker.example a .env y toca dos cosas:
- Los dominios (
TIENDA_URL,PANEL_URL,BACKEND_URL). De ahí salen los CORS y los enlaces de los correos. - La contraseña de Postgres. La de ejemplo es
comercio; cámbiala.
Los secretos de sesión puedes dejarlos vacíos: el contenedor genera unos la primera vez y los guarda en su volumen. Ponlos a mano solo si vas a levantar varias réplicas del backend — cada una generaría los suyos y las sesiones dejarían de valer al saltar de una a otra.
Cambias el .env, reinicias, y ya:
docker compose -f docker-compose.full.yml up -dNada de esto obliga a reconstruir imágenes, y no es lo que sale por defecto
con Next: las variables que llegan al navegador se incrustan al compilar, de
modo que corregir un NIF exigiría reconstruir el panel entero. Aquí el servidor
lee la configuración al arrancar y se la entrega al navegador con cada página
(apps/admin/src/lib/config.ts), que es el patrón que documenta Next para
“construir una vez y desplegar donde sea”. Lo mismo vale para el nombre de la
tienda, el color de marca y los datos fiscales de las facturas.
Lo único que sigue atado al momento de construir es el tema del escaparate.
Si pones un proxy delante, avisa
Sección titulada «Si pones un proxy delante, avisa»En cuanto le pongas un dominio con HTTPS vas a tener delante un proxy inverso
—Apache, nginx, Cloudflare, Traefik—. Cuando lo hagas, pon esto en el .env:
TRUST_PROXY=1Si el backend se ve directamente desde fuera, déjalo vacío.
Parece un detalle y no lo es. El formulario de contacto es la única puerta de la tienda por la que se puede escribir sin haber entrado con una cuenta, así que tiene un límite: cinco mensajes cada diez minutos desde la misma IP. La pregunta es de dónde sale esa IP.
Con un proxy delante, todas las visitas le llegan al backend desde la misma dirección: la del proxy. Si no le dices que está ahí, tu tienda entera comparte un solo contador y la sexta persona que escriba en diez minutos se lleva un bloqueo sin haber hecho nada. Un límite mal puesto es peor que no tener ninguno.
Y al revés también duele. La IP real del visitante viaja en una cabecera
(X-Forwarded-For) que el propio visitante puede escribir. Si no hay proxy
que la reemplace, cualquiera se inventa una distinta en cada petición y el
límite deja de existir: comprobado aquí, con el límite en cinco, ocho peticiones
rotando esa cabecera pasaban las ocho. Por eso hay que decirlo, y por eso lo
seguro es lo que sale por defecto.
Con Docker no tienes que hacer nada más: la variable ya está cableada al contenedor del backend. Sin Docker, ponla en el entorno del proceso.
Cambiar de tema
Sección titulada «Cambiar de tema»TEMA=<carpeta de themes/> en el .env, y reconstruir el escaparate:
docker compose -f docker-compose.full.yml build tiendadocker compose -f docker-compose.full.yml up -dEl panel lee el catálogo de temas del disco (./themes va montado), así que ahí
no hace falta reconstruir nada.
De dónde vienen las fotos
Sección titulada «De dónde vienen las fotos»La otra variable que se resuelve al construir, por el mismo motivo que el tema: Next incrusta la lista en el paquete.
IMAGE_HOSTS=cdn.mitienda.com,*.midominio.comSon los servidores de los que el escaparate acepta imágenes. Si las fotos las
sirve el propio backend desde su /static, pon ahí su dominio.
Si lo dejas vacío no se rompe nada, pero se apaga el optimizador: las fotos
se ven —las carga el navegador, directamente— y pierdes el recorte automático y
el WebP. Es a propósito. La alternativa cómoda es aceptar imágenes de cualquier
sitio, y eso convierte tu tienda en un mandado: /_next/image?url=… hace que tu
servidor se descargue la dirección que le pidan y la procese. Con eso se puede
sondear tu red interna desde fuera —el servidor tarda distinto según el puerto
conteste o no—, tumbar el proceso con una imagen que al descomprimirse llena la
memoria, o simplemente gastarte el ancho de banda.
Estaba así hasta hace poco, y se comprobó: pidiendo una imagen de un servidor
cualquiera de internet, la tienda la descargaba y devolvía 200.
Al cambiarla, reconstruye el escaparate igual que con el tema.
Qué se guarda y dónde
Sección titulada «Qué se guarda y dónde»Cuatro volúmenes, y conviene saber cuál es cuál antes de borrar alguno:
| Volumen | Qué hay |
|---|---|
db |
La base de datos. Todo. |
static |
Las imágenes subidas desde el panel. |
datos |
Secretos generados y la clave publicable. |
Sin Docker — en tu propio servidor
Sección titulada «Sin Docker — en tu propio servidor»Lo que hace Docker por dentro, a mano. Hace falta Node 22 o más y un Postgres.
npm ci # SIEMPRE ci, no installnpm run build -w @pcreative/commerce-backend # compila a apps/backend/dist
cd apps/backendcp .env.example .env # DATABASE_URL y poco másnpm run preparar # instala o migra, y prepara la tiendanpm start # corre el compiladopreparar mira la base y decide solo: si está vacía crea el esquema entero
(173 tablas, sin nada más instalado); si ya tiene cosas, aplica lo que falte.
Y en los dos casos deja la tienda en condiciones de vender: la fila de la
tienda, un canal de venta y la clave publicable que el escaparate necesita para
hablar con ella. Es idempotente — se puede correr en cada arranque, que es lo
que hace el contenedor.
Después, el asistente en el navegador, igual que con Docker.
Detrás de un gestor de procesos o un balanceador
Sección titulada «Detrás de un gestor de procesos o un balanceador»El servidor tiene dos puertas de salud, y no son la misma cosa:
/salud→ 200 mientras el proceso responda. Para «¿está vivo?»./listo→ 200 solo si puede atender clientes. Pasa a 503 en cuanto empieza a cerrarse, y también si algún plugin no cargó al arrancar — un servidor que vende a otro precio no está listo.
El gestor de procesos mira /salud para decidir si reinicia; el balanceador
mira /listo para decidir si manda tráfico. Al revés se pierden peticiones:
un balanceador que mira /salud sigue mandando clientes a un servidor que ya se
está yendo, y un gestor que mira /listo mata el proceso mientras termina un
cobro. Con PCC_MARGEN_CIERRE_MS=5000 el servidor espera ese margen entre
avisar por /listo y cerrar el puerto, para que el balanceador se aparte.
Para desarrollar
Sección titulada «Para desarrollar»Igual, pero sin compilar y recargando al guardar:
docker compose up -d # solo Postgresnpm cicd apps/backend && npm run dev # tsx watchcd apps/admin && npm run devLo que aún no hay
Sección titulada «Lo que aún no hay»- Un comando
npx create-pcreative-commercepara quien no quiera saber nada de Docker. Ahora sería un envoltorio fino de lo de arriba. - Botones de despliegue para Coolify, Railway o DigitalOcean.
- Hosting compartido. Y esto no va a llegar: Node no está instalado en los hostings de 3 € donde vive medio WooCommerce. Es la desventaja estructural frente a PHP y no se arregla con un instalador mejor.