Skip to content
pcreative Commerce

Installation

pcreative Commerce is downloaded as a .zip from pcreativecommerce.dev and installed on your own server.

pcreative Commerce is downloaded as a .zip from pcreativecommerce.dev and installed on your own server. Starting and configuring are different jobs: starting is one command; configuring always happens in the browser wizard, without a terminal.

What you need: a Linux machine with Docker and Docker Compose (the installer puts them in if they are missing), 2 GB of RAM and at least 12 GB of free disk — the installer refuses to carry on with less. With under 4.5 GB of RAM and no swap, it creates a 4 GB swap file (/swapfile) before starting, because the build peak has nowhere else to go. It does not run on shared hosting: it needs Node and PostgreSQL, and those plans offer neither.


Install

The installer is downloaded next to the package. On your server:

sh instalar.sh pcreative-commerce-*.zip

It checks the package's checksum if the .sha256 file is next to it, checks the machine, installs whatever is missing (Docker included), unpacks the package into its folder, creates the .env from .env.docker.example with a random database password and brings up five pieces: Postgres, the backend, the admin panel, the seller panel and the storefront. When it finishes it tells you where to go, and it tells you to finish the setup straight away.

The programs come already built: your machine compiles nothing. The first run takes a few minutes because it downloads the backend dependencies; after that it starts in about ten seconds. On every start the backend creates the schema if the database is empty —or applies whatever is missing— and prepares the store, and it is safe to repeat. Running the installer a second time does not delete data: it updates what is already running.

By hand

If you would rather not use the installer, inside the unpacked folder:

cp .env.docker.example .env
# set POSTGRES_PASSWORD in .env (letters and numbers only)
docker compose up -d --build

In the package the file is called docker-compose.yml; in the source repository the same file is docker-compose.full.yml, so there every command takes -f docker-compose.full.yml.

Install it the moment it is up

Opening the panel drops you straight into the wizard. There is no code to type and nothing to unlock: pcreative Commerce is free, and the first screen you see is not going to pretend otherwise.

What that means is worth saying plainly. Until an administrator account exists, whoever reaches the wizard first takes over the store. That is the same deal as every other shop software you install in a browser, and the only thing that closes the window is finishing the wizard: from that moment on the five setup routes answer «this store is already installed» and nothing else. So install it the moment the installer finishes — not tomorrow.

If there is a gap between bringing it up and getting to a browser, keep the panel off the open internet until you are done: the installer already binds every port to 127.0.0.1, so reach it over an SSH tunnel (ssh -L 7001:localhost:7001 you@yourserver) instead of publishing it first and installing later. If you would rather be certain, block the route in your reverse proxy until you have created your account.

The wizard itself is the checks, your store name, your account and, if you want it, a demo catalogue so you can watch the whole loop work.

If that window worries you —and on a server with a public name it should— skip the wizard entirely with the section below.

Better than being quick is not having a window to be quick in. Put the administrator in the configuration and the store installs itself on the first start: it creates the account, names the store from STORE_NAME and closes the installation before anything is listening for a browser. The wizard then refuses to open at all, and says so.

PCC_ADMIN_CORREO=you@yourdomain.com
PCC_ADMIN_CONTRASENA=at-least-ten-characters
PCC_ADMIN_NOMBRE=Ana
PCC_ADMIN_APELLIDOS=Ruiz

Put the password in a file, not in the .env. Every one of those variables has a _FILE twin that holds the PATH of a file to read the value from, which is what Docker secrets are for. What is in the .env is also in docker inspect; what is in a secret is not:

secrets:
  clave_admin:
    file: ./secretos/clave-admin
services:
  backend:
    secrets: [clave_admin]
    environment:
      PCC_ADMIN_CONTRASENA_FILE: /run/secrets/clave_admin

The backend drops the password from its own environment as soon as it has read it, so no extension loaded afterwards can see it, and it never writes it to the log — not even a piece of it.

What happens in the awkward cases, so there are no surprises:

  • Already installed. Nothing is touched and nothing is said. You can leave the variables in the .env for good.
  • Set wrong — an address that is not an email, a password under ten characters, a variable and its _FILE twin both set, or a _FILE pointing at something it cannot read: the backend refuses to start and says why. It does not install halfway, and it does not quietly fall back to leaving the wizard open, which is the very thing you were trying to avoid.
  • Started twice. The second start creates nothing: there is one administrator, not two.
  • A start that died halfway, with the account created but the installation not closed: the next start closes it and creates no one.

Country and currency

The wizard does not ask for them. On its first start the backend creates the store's region with the country in PCC_PAIS and the currency in PCC_MONEDA, which default to es and eur. If your store sells in another currency, set both in the .env before the first start (for example PCC_PAIS=us and PCC_MONEDA=usd); once the region exists, changing them does not rewrite it.

Locked out of the panel

rescate creates or repairs panel access from the server's console. Inside Docker:

docker compose exec backend node dist/src/propio/cli.js rescate
docker compose exec backend node dist/src/propio/cli.js rescate alta you@yourdomain.com

Without an order it lists who has access; alta, clave and cerrar are explained in the CLI reference. Outside Docker it is npm run rescate in apps/backend, which reads its .env on its own.

Ports only open on your machine

The storefront, the panels and the backend listen on 127.0.0.1, not on the internet. This is deliberate: Docker opens its ports ahead of the system firewall, so a port published "for everyone" stays open even when ufw says otherwise.

On a remote server, a tunnel from your computer is all you need to use the wizard (the installer prints the command for the panel and the storefront; add the seller panel if you want to see it too):

ssh -L 7001:localhost:7001 -L 3000:localhost:3000 -L 7002:localhost:7002 user@your-server

To follow the startup:

docker compose logs -f backend

Putting it live

Put a reverse proxy with HTTPS in front. With Caddy, which gets certificates on its own, the Caddyfile is:

shop.yourdomain.com {
    reverse_proxy 127.0.0.1:3000
}

admin.yourdomain.com {
    redir / /admin
    reverse_proxy 127.0.0.1:7001
}

sellers.yourdomain.com {
    reverse_proxy 127.0.0.1:7002
}

api.yourdomain.com {
    reverse_proxy 127.0.0.1:9000
}

And in the .env the installer created:

  1. The domains (TIENDA_URL, PANEL_URL, VENDEDORES_URL, BACKEND_URL), with https://. CORS and the links in your emails are derived from these; VENDEDORES_URL is where the welcome email of an accepted author or seller points.
  2. TRUST_PROXY=1, because there is now a proxy in front (see below).

The database password is already random and the database lives on an internal Docker network: only the backend can see it.

Change the .env, restart, done:

docker compose up -d

None of this requires rebuilding anything. The panel and the backend read their configuration on startup. The backend reads the whole .env, so any variable in apps/backend/.env.example —email, AI, payout rails…— also works in Docker. Only TEMA and IMAGE_HOSTS require rebuilding the storefront.

The application containers run without administrator permissions and cannot gain them later.

The secrets, and why datos matters

You can leave JWT_SECRET and COOKIE_SECRET empty: the container generates a pair on first run and keeps it in the datos volume (secretos.env).

What you cannot do is leave the example values in. apps/backend/.env.example ships JWT_SECRET=cambia-esto, and that string is in the repository, which means it is in everybody's hands. With it, whoever knows it can read everything this store encrypts. So the backend refuses to start with it and tells you how to generate a real one:

✗ [arranque] JWT_SECRET tiene el valor de ejemplo, que viene escrito en el
  `.env.example` y por tanto lo conoce cualquiera.

That is deliberate and it is not a warning. A store running on a published key works perfectly until the day it does not, and by then it has real gateway credentials and real payout accounts in it. Refusing to start turns a silent hole into an error you read on the first deploy, when it costs one command:

JWT_SECRET=$(openssl rand -hex 32)
COOKIE_SECRET=$(openssl rand -hex 32)

The comparison is exact and against those published values only: a secret you typed yourself starts fine, however ugly it looks. Empty is not the same thing — with no key at all nothing sensitive is written or read, the panel says so to your face, and the store still starts.

Panel and customer sessions do not depend on them: they are random tokens stored in the database. What does depend on them is encryption. JWT_SECRET —or PCC_CLAVE_CIFRADO, if you set it— is the key that encrypts what the store keeps in the database: the payment gateway keys saved from the panel, the digital keys and codes you sell, and the sellers' payout accounts. It also signs private download links and licences. That has two consequences:

  • Several backend replicas must share the same value. Set JWT_SECRET (or PCC_CLAVE_CIFRADO) by hand: with different secrets, one replica cannot read what another one encrypted.
  • If you lose the datos volume, the next start generates new secrets and everything encrypted becomes unreadable. Back up datos (at least secretos.env), or set PCC_CLAVE_CIFRADO yourself and keep it somewhere safe. See Backups.

Tax details on invoices

They are entered in the panel, under Settings → Billing. What is saved there comes first, for both the panel and the invoice customers download from the storefront. The COMPANY_* variables in the .env are only the fallback while nothing has been saved.

If you rely on that fallback, the invoice number prefix is INVOICE_PREFIX, the same name for the panel, the backend and Docker. The names it had before (COMPANY_INVOICE_PREFIX in the backend, NEXT_PUBLIC_INVOICE_PREFIX in the panel) are still read, so an existing .env keeps working.

Once it is installed, shut the route at the proxy too

With the store installed, the five /setup routes answer 409 and refuse to do anything. The panel checks that for you: on the dashboard it reviews «the setup wizard (on this server)» and, if any of them ever answered normally, it says so in red. Be clear about what that check is worth — it asks this server, over its own loopback address. It proves the door is shut in the backend; it says nothing about whether that address is reachable from the internet, because from inside the machine there is no honest way to know.

That part is yours, and it costs one block in the reverse proxy:

location /setup { return 404; }
<LocationMatch "^/setup">
  Require all denied
</LocationMatch>

Do it after you have created your account. It changes nothing while the store works and it removes the route from the internet for good.

If you put a proxy in front, say so

As soon as you give it a domain with HTTPS there will be a reverse proxy in front —Apache, nginx, Cloudflare, Traefik—. When you do, set this in the .env:

TRUST_PROXY=1

If the backend is reachable directly from outside, leave it empty.

It looks like a detail and it is not. The backend counts several things per IP: panel and store logins (30 attempts every 15 minutes), sign-ups (10 an hour), password reset requests, calls to the setup wizard, and contact-form messages (five every ten minutes). The question is where that IP comes from.

With a proxy in front, every visit reaches the backend from the same address: the proxy's. If you do not tell it the proxy is there, your whole store shares one counter, and the sixth person to write in ten minutes gets blocked without having done anything. A badly set limit is worse than none.

The other way round hurts too. The visitor's real IP travels in a header (X-Forwarded-For) that the visitor can write themselves. If there is no proxy replacing it, anyone can invent a different one on every request and the limit stops existing: tested here, with the limit at five, eight requests rotating that header all got through. That is why you have to say so, and why the safe value is the default.

TRUST_PROXY=1 only trusts proxies on private networks (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 and their IPv6 equivalents), which covers a proxy on the same machine or the Docker network. A proxy with a public address —Cloudflare is the usual case— has to be listed in PCC_PROXIES_DE_CONFIANZA, comma separated, as addresses or ranges (173.245.48.0/20,…). Otherwise the backend takes Cloudflare's address for the visitor's.

With Docker you do not have to do anything else: the backend reads both variables from the .env.

Changing the theme

From the panel, under Themes. The package ships four —base, mascotas, cinematografico and dulce-obrador—, all in English and Spanish, with /en and /es in the address and English by default. You can upload others as .zip. When you activate one, the panel builds it on your server without taking the store down: activating a theme is the only time your machine compiles anything, so 4 GB of RAM or some swap helps there.

Storefront photos

They always show. The storefront in the package does not crop or convert photos to WebP on its own, because doing that means accepting images from any server, and that lets anyone use your store to download whatever they ask for. If you want optimisation, serve photos from a CDN that does it.

What is stored, and where

The volumes, and it is worth knowing which is which before deleting any of them:

VolumeWhat is in it
dbThe database. Everything.
staticImages uploaded from the panel.
privadoFiles customers upload when personalising a product, and the files of downloadable products. Never served publicly.
datosGenerated secrets (secretos.env) and the publishable key. Without the secrets, what is encrypted in the database cannot be read.
temasThe themes: the built-in ones and any you install from the panel.
temas-en-marchaWhat the panel builds and runs for each theme, and which theme is active.

The two theme volumes are named volumes the panel writes to: that is why what you install survives rebuilding the image. The first time, Docker seeds temas with the themes the image ships with. If you delete temas-en-marcha you lose no installed theme, but you do lose what was built and which one was active: you have to activate it again.

Behind a process manager or a load balancer

The server has two health endpoints, and they are not the same thing:

  • /salud → 200 as long as the process responds. For "is it alive?".
  • /listo → 200 only if it can serve customers. It switches to 503 the moment shutdown begins, and also if any plugin failed to load at startup — a server selling at a different price is not ready.

The process manager watches /salud to decide whether to restart; the load balancer watches /listo to decide whether to send traffic. The other way round loses requests: a balancer watching /salud keeps sending customers to a server that is already leaving, and a manager watching /listo kills the process while it finishes a payment. With PCC_MARGEN_CIERRE_MS=5000 the server waits that long between flagging /listo and closing the port, so the balancer can step aside.


What is not there yet

  • Deploy buttons for Coolify, Railway or DigitalOcean.
  • Installing without Docker. It can be done, but it is not documented or tested yet.
  • Shared hosting. And this one is not coming: Node is not installed on the €3 hosting plans where half of WooCommerce lives. It is the structural disadvantage against PHP, and a better installer does not fix it.