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-*.zipIt 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.
- Storefront → http://localhost:3000
- Admin panel → http://localhost:7001/admin
- Seller panel (marketplace authors and sellers) → http://localhost:7002
- Backend → http://localhost:9000
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 --buildIn 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.
Install it without a wizard at all (recommended with Docker)
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=RuizPut 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_adminThe 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
.envfor good. - Set wrong — an address that is not an email, a password under ten
characters, a variable and its
_FILEtwin both set, or a_FILEpointing 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.comWithout 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-serverTo follow the startup:
docker compose logs -f backendPutting 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:
- The domains (
TIENDA_URL,PANEL_URL,VENDEDORES_URL,BACKEND_URL), withhttps://. CORS and the links in your emails are derived from these;VENDEDORES_URLis where the welcome email of an accepted author or seller points. 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 -dNone 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(orPCC_CLAVE_CIFRADO) by hand: with different secrets, one replica cannot read what another one encrypted. - If you lose the
datosvolume, the next start generates new secrets and everything encrypted becomes unreadable. Back updatos(at leastsecretos.env), or setPCC_CLAVE_CIFRADOyourself 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=1If 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:
| Volume | What is in it |
|---|---|
db | The database. Everything. |
static | Images uploaded from the panel. |
privado | Files customers upload when personalising a product, and the files of downloadable products. Never served publicly. |
datos | Generated secrets (secretos.env) and the publishable key. Without the secrets, what is encrypted in the database cannot be read. |
temas | The themes: the built-in ones and any you install from the panel. |
temas-en-marcha | What 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.