This guide walks through booting the full OpenLinker stack in Docker with a single
command and clicking through an end-to-end PrestaShop → OpenLinker → Allegro
flow. It expands on the short “One-command demo” section in the root
README.md.
The demo boots, from a clean checkout: PostgreSQL, Redis, MySQL, a seeded
PrestaShop (with the OpenLinker module pre-mounted), and the full OpenLinker app
tier — API + Worker + admin UI.
Local evaluation only. Credentials are intentionally not production-safe and
the stack is not hardened (no TLS, default passwords). Do not expose it publicly.
Need to make the demo reachable under a real domain instead (reverse proxy +
TLS, rotated credentials, DNS)? See the
public-domain demo deployment guide.
Docker and Docker Compose ≥ 2.24 (docker compose version). The demo
overlay uses !reset / merge features from 2.24+.
pnpm and Node.js LTS (only to run the pnpm demo:* wrapper scripts).
~4 GB free disk for the built images and a few minutes for the first build
(the whole monorepo is compiled inside the image; the build is from your local
checkout, not a pre-built registry image).
Free host ports:8090 (UI), 3000 (API), 8080 (PrestaShop), 8082
(WooCommerce), 5432 (Postgres), 6379 (Redis), 3306 (MySQL), 3307
(WooCommerce MySQL). 8081 (phpMyAdmin) is only needed if you opt into the
devtools profile — see below.
⚠️ Shared volumes with the dev stack. The demo shares the same Compose
project (openlinker) and data volumes as pnpm dev:stack:up. On a machine
that already runs the local dev stack, the demo reuses and can clobber that
data — the two flows are not isolated. Stop (and, if needed, wipe) one
before running the other: pnpm dev:stack:down or pnpm demo:down -v.
⚠️ Upgrading from a pre-#1411 stack (one-time). Named volumes were renamed
_data → -data and PostgreSQL moved 16 → 17, so your first pnpm demo:up
after pulling boots with an empty database and no warning — the old data
lives on in orphaned openlinker_*_data volumes. Confirm you don’t need it,
then reclaim the disk:
Docker Compose auto-loads a root .env file for ${VAR} interpolation. One
variable is required; everything else has a working default baked into the
Compose files.
Encryption key for the integration_credentials store. Migration 1795000000000-encrypt-integration-credentialsfails closed under NODE_ENV=production if it is unset and aborts the whole boot; the API/Worker also need it to decrypt connection credentials at runtime. Base64-encoded 32 bytes.
.env is gitignored — never commit real secrets. .env.example ships an empty
OPENLINKER_CREDENTIALS_ENCRYPTION_KEY= line, so drop it and append the
generated value instead of >> .env directly — appending onto the example as-is
would leave two lines with the same key (harmless, since env-file parsing is
last-wins, but confusing to anyone editing .env by hand):
These are hardcoded in docker-compose.yml / docker-compose.demo.yml with
demo-safe values. Listed here so you know what’s in play if you want to override:
Variable
Value in the demo
Purpose
NODE_ENV
production
Production posture for the app tier.
OL_BOOTSTRAP_ADMIN_PASSWORD
admin
Keeps the seeded admin login admin / admin under NODE_ENV=production.
OL_CORS_ORIGIN
http://localhost:8090
API CORS allow-list — must match the UI origin or login fails with a CORS “NetworkError”.
WEB_URL
http://localhost:8090 (tracks WEB_HOST_PORT)
Base URL used to build confirm-email / reset-password links emailed to users - stays in sync with the web service’s published port instead of a second hardcoded default.
VITE_API_BASE_URL
http://localhost:3000
Baked into the UI bundle at build time — the browser-reachable API origin.
DB_* / REDIS_*
postgres / redis (service names)
App tier reaches Postgres/Redis over the Compose network.
JWT_SECRET, JWT_EXPIRES_IN
dev values
Auth token signing.
OL_PII_HASH_SALT
dev value
Customer-identifier hashing in sync jobs.
VITE_API_BASE_URL is a build-time input — changing it requires rebuilding
the web image (pnpm demo:up rebuilds when the arg changes).
Optional: session recording on a public demo instance
OpenLinker ships no telemetry by default. Session recording (PostHog)
only activates on an instance where the operator has both set
OL_DEMO_MODE=true and explicitly configured OL_POSTHOG_KEY — a self-hosted
install or a local pnpm demo:up run never contacts PostHog. When both are
set, GET /system/config surfaces a demoIntegrations.posthog block that the
frontend uses to load posthog-js (dynamically, so it never ships in the
default bundle) — and only for an account that has accepted session recording.
On a demo instance recording is a condition of use, not an optional consent
(#1938): the registration form discloses it and creating the account accepts it,
an account created before the rule is redirected to /consent (continue, or sign
out), and the API refuses every other route for a demo viewer until it does.
Declining means not using the demo — which carries no cost, since the demo is
free and holds only synthetic data.
Variable
Purpose
OL_POSTHOG_KEY
PostHog project API key (publishable, write-only ingestion key — never a personal/private key). Unset by default.
OL_POSTHOG_HOST
PostHog ingestion host. Defaults to https://eu.posthog.com when OL_POSTHOG_KEY is set.
The two variables above enable session recording only. Named product
events (demo_offer_create_attempted, demo_orders_viewed, …) are a separate,
DB-only setting with no env var — an env-configured demo emits none of them
until an admin turns them on. After the stack is up:
Sign in as an admin and go to /settings → PostHog.
Enable the Product events master toggle.
Tick the event groups you want (they are derived from the event catalog,
so the list is always current).
Both the master toggle and an event’s own group must be on before that event
fires. Until then the instrumentation is inert — the demo works normally, it
just reports nothing. See docs/analytics-events.md
for the catalog and the group model.
Only run session recording against synthetic seed data. This is a hard
requirement, not a precaution. Recording masks passwords only (#1877)
— every other input value and all rendered page text is captured verbatim,
because a replay that masks everything shows layout and no content, which
makes the recording useless.
So a demo instance pointed at real shop data ships real buyer PII to
PostHog cloud: customer names and addresses on the orders and customers
surfaces, and tax IDs on the KSeF / invoicing surfaces — plus the non-text
signal that was always exposed (order IDs in URLs, product image content,
layout).
Never set OL_POSTHOG_KEY on an instance connected to a live
PrestaShop / Allegro / Erli / WooCommerce store with real customer data.
If you need recordings against a realistic dataset, seed it synthetically
rather than relaxing this rule.
There is no in-product opt-out (#1938): the Settings tile and the banner’s
“Disable” affordance were removed when recording became a condition of use, and
the acceptance is not a consent that Art. 7(3) withdrawal attaches to.
Servicing a withdrawal request means calling
PATCH /auth/me/analytics-consent with false for that account and deleting
the recording in PostHog — the account cleanup below removes the OL account,
not the recordings.
See ADR-032 (docs/architecture/adrs/032-demo-only-vendor-neutral-analytics-config-seam.md, merging via #1410) for the design rationale.
Boot order is enforced by depends_on (not the CLI service list):
Postgres healthy → one-shot migrate runs to completion → api + worker
start; PrestaShop and WooCommerce auto-install in parallel; web is static
(nginx) and needs nothing. Migrations run automatically — no manual
migration:run.
The PrestaShop admin folder is admin-dev (the post-install step renames the
randomized install folder), not/admin.
phpMyAdmin (PrestaShop MySQL browser) is a devtools-only auxiliary service, not part of the default boot — start it separately with pnpm demo:devtools:up, then reach it at http://localhost:8081 (root / root).
Add new webservice key → Generate! → set Status = Enabled → grant
permissions (the simplest is to check the “all” / view+modify boxes for the
resources you’ll sync: products, combinations, stock_availables,
orders, customers, addresses, …) → Save. Copy the generated key.
Nothttp://localhost:8080. The API/Worker containers reach PrestaShop over the Compose network by its service name; localhost inside a container is the container itself.
Storefront URL
http://prestashop
Same reason — OpenLinker downloads product image bytes server-side (then re-uploads them to the marketplace CDN), so the image base must be container-reachable.
Webservice key
(paste from 5.1)
Shop ID
(blank)
Single-shop install.
The demo pre-configures PrestaShop so the http://prestashop host resolves the
shop without a canonical-domain redirect (a prestashop-domain ps_shop_url
row + PS_CANONICAL_REDIRECT=0, added by a post-install step), so the
Verify credentials step (GET /api/products?limit=1) succeeds.
Known limitation: with the Storefront URL set to http://prestashop, image
download works from the app tier, but your browser can’t resolve
prestashop, so product thumbnails in the OpenLinker UI won’t render. This is a
tracked follow-up (decoupling the server-side download base from the browser
display base). For the demo, prefer a working image sync over rendered
thumbnails.
Allegro vs. Erli — the Storefront URL requirement differs. The guidance
above (http://prestashop) only holds for Allegro: OpenLinker downloads
the product image bytes server-side and re-uploads them to Allegro’s CDN, so
the URL only needs to be container-reachable, not public.
Erli does not re-upload images — its servers fetch the product image URL
directly, so for an Erli connection the Storefront URL must be a real,
publicly-reachable HTTPS URL. http://prestashop will silently fail image
sync to Erli. For local testing, front PrestaShop with a quick tunnel (e.g.
cloudflared tunnel --url http://localhost:8080); for a persistent
deployment, use the reverse-proxy/public-domain overlay
(docker-compose.proxy.yml) described in
docs/public-domain-demo-deployment-guide.md.
storefrontBaseUrl is a single, connection-level setting shared by every
destination synced from that PrestaShop master. If you’re running both
Allegro and Erli off the same connection, set it to the public HTTPS URL —
a public URL also satisfies Allegro’s (looser) container-reachability
requirement, but a container-internal URL does not satisfy Erli’s.
After connecting, OpenLinker syncs the seeded catalog (6 products + variants). See
them under Products.
Allegro is an external service — the credentials come from an app you register
in the Allegro developer portal; they cannot be seeded. Use the sandbox for
testing.
Create a sandbox account at https://allegro.pl.allegrosandbox.pl.
In the sandbox developer portal
(https://apps.developer.allegro.pl.allegrosandbox.pl), create an app of type
“application with a browser-based flow (Authorization Code)” — only that
type has a Client Secret.
Register the redirect URI exactly as the wizard shows:
http://localhost:8090/integrations/allegro/connect/callback
In OpenLinker: Connections → New → Allegro, paste Client ID + Client
Secret, and on the Environment step choose Sandbox. Complete the OAuth
redirect.
Seller defaults — before creating offers, fill the seller-defaults section
on the Allegro connection’s edit page: ship-from location, Responsible
Producer (GPSR), and safety information (GPSR). Offer creation fails with
SELLER_DEFAULTS_NOT_CONFIGURED until these are set. The values come from your
Allegro account.
Required category parameters (e.g. “Stan”/Condition). Most Allegro
categories require offer parameters like Stan. In the single-offer wizard
(Listings → “Create offer”) you pick a category and fill the parameter step
(set Stan = Nowy). Supplying these in the bulk flow requires
#1367 /
#1370.
Products synced:Products lists the 6 seeded PrestaShop items, each with a
prestashop source mapping.
Create an offer (with an Allegro sandbox connection): Listings →
“Create offer” → pick a product/variant → resolve/pick the Allegro category →
fill required parameters (Stan = Nowy) → submit. The Worker processes the
marketplace.offer.create job; watch it with:
pnpmdemo:down-v# stop and WIPE the data volumes (fresh start next time)
Because the demo shares the openlinker Compose project with pnpm dev:stack:up,
-v wipes volumes shared with the dev stack too — see the warning in section 1.
10. Running on a shared server / multiple instances
By default every published port binds to 127.0.0.1 and every container name
is prefixed openlinker- — safe on a single-user laptop, but two things to
know before running this on a shared/public host (#1400):
Loopback binding is the default, not an accident.postgres / redis /
mysql / phpmyadmin / prestashop / api all publish on 127.0.0.1 so
they’re never reachable from outside the host just because a firewall
wasn’t configured — put a reverse proxy in front of web (and api, if the
browser needs to reach it directly) for anything beyond localhost access.
Override the bind interface with OL_BIND_ADDRESS in .env only if a
service genuinely needs to be reachable beyond loopback.
Running a second instance alongside an existing one (e.g. a per-PR
preview environment, or a staging + demo pair on the same host): set a
distinct COMPOSE_PROJECT_NAME (renames the project and every
container_name) plus distinct *_HOST_PORT overrides
(POSTGRES_HOST_PORT, REDIS_HOST_PORT, MYSQL_HOST_PORT,
PHPMYADMIN_HOST_PORT, PRESTASHOP_HOST_PORT,
WOOCOMMERCE_MYSQL_HOST_PORT, WOOCOMMERCE_HOST_PORT, API_HOST_PORT,
WEB_HOST_PORT) in that instance’s own .env — see .env.example for the
full list and defaults.
pnpm dev:stack:seed-woocommerce / pnpm dev:stack:wc-credentials go through
docker compose exec woocommerce, so they resolve the container through
Compose’s own project scoping and respect a custom COMPOSE_PROJECT_NAME
(#2109). Run them from the same directory as that instance’s .env.
We use Google Analytics to understand traffic on openlinker.io and docs.openlinker.io.
Nothing is loaded or sent to Google unless you accept.
Privacy policy