Skip to content
Last updated Give Feedback

Customer Onboarding

You’ve completed checkout, your inbox has a “Licența ta Ecommus … este activă” email, and now you need to install ecommus on your server. This page walks you from that email to a working install in under 60 minutes.

If you’re evaluating ecommus and haven’t bought yet, start with Introduction instead.

The license-delivery email contains:

  1. JWT (license token) — single line of base64. Don’t share it; don’t paste it in screenshots; it’s how the framework knows your install is paid.
  2. Niche package(s) you boughtservices, real-estate, travel, or ecommerce (or a combination).
  3. Tiercommunity, pro, or enterprise. Drives which plugins/themes you can install.
  4. Subscription period3mo / 9mo / 12mo. The expiry timer starts now.
  5. An npm token — used to download premium plugins from the private registry. Often delivered in a separate email after domain confirmation.

Keep all of these somewhere safe (1Password / Vault / pass). If you lose them, your operator can resend, but it’s friction.

Minimum, all required:

  • A VPS running Ubuntu 22.04 or 24.04 LTS — recommended specs 4 vCPU / 8 GB RAM / 80 GB SSD for a single tenant. Bigger if you plan to host multiple shops.
  • A domain you control (e.g. myshop.com). Three subdomains will be auto-created: storefront (myshop.com), admin (admin.myshop.com), api (api.myshop.com).
  • DNS access — Cloudflare, Route 53, or your registrar’s panel.
  • Stripe account — for accepting customer payments. Test mode is fine to start; live mode requires KYC.
  • An email sending account — Resend (recommended free tier, 3000 emails/mo) or any SMTP provider.

You don’t need to know docker or systemd to get started — the install script handles it.

If you ordered the recommended VPS (Hetzner CCX23 or equivalent), you’ll receive an IP + root password. From your laptop:

Terminal window
# Replace with values from your purchase email
export VPS_IP=1.2.3.4
export ECOMMUS_DOMAIN=myshop.com
export ECOMMUS_ADMIN_EMAIL=ops@myshop.com
# Run the bootstrap script (system update + docker + caddy + firewall + ssh hardening)
ssh root@$VPS_IP \
ECOMMUS_DEPLOY_USER=ecommus \
ECOMMUS_DEPLOY_PUBKEY="$(cat ~/.ssh/id_ed25519.pub)" \
ECOMMUS_DOMAIN=$ECOMMUS_DOMAIN \
ECOMMUS_ADMIN_EMAIL=$ECOMMUS_ADMIN_EMAIL \
bash -s < <(curl -fsSL https://raw.githubusercontent.com/MDLABS-cmd/ecommus/master/infra/provision.sh)

The script prints “ecommus provision: run mode complete” when done. About 3 minutes on a fresh server.

Don’t have an SSH key yet? Run ssh-keygen -t ed25519 -C "you@laptop" on your laptop first. Press Enter through the prompts (default location, no passphrase for first install — add one later).

Step 2 — DNS records (5 min + propagation)

Section titled “Step 2 — DNS records (5 min + propagation)”

Add these records at your DNS provider:

TypeNameValueTTL
A@ (apex)your VPS IP300
A* (wildcard)your VPS IP300
CAA@0 issue "letsencrypt.org"3600
CAA@0 issuewild "letsencrypt.org"3600

If you use Cloudflare: set the orange cloud to DNS only (grey) for now. Caddy needs direct port-80 access to issue Let’s Encrypt certs. You can flip the proxy back on after the first cert is issued.

Verify propagation (~5 min):

Terminal window
dig +short api.$ECOMMUS_DOMAIN
# Should print your VPS IP

Step 3 — install ecommus + paste your license (10 min)

Section titled “Step 3 — install ecommus + paste your license (10 min)”

SSH in as the deploy user:

Terminal window
ssh ecommus@$VPS_IP

Pull the source + install dependencies:

Terminal window
cd /opt/ecommus
git clone https://github.com/MDLABS-cmd/ecommus.git repo
cd repo
git checkout v1.0.0 # or the latest tag from the release notes
# Authenticate npm to the private registry using YOUR token from the second email
npm config set "@ecommus:registry" "https://npm.ecommus.ro" --location=user
npm config set "//npm.ecommus.ro/:_authToken" "YOUR_NPM_TOKEN_HERE" --location=user
# Install (this pulls premium plugins your tier is licensed for)
npm install

Now create your .env:

Terminal window
cp infra/.env.production.template /opt/ecommus/.env
chmod 600 /opt/ecommus/.env
ln -sf /opt/ecommus/.env .env
nano /opt/ecommus/.env

Replace every TODO_GENERATE with a strong secret:

Terminal window
# Quick generators (paste output into the matching .env line)
echo "POSTGRES_PASSWORD=$(openssl rand -base64 32 | tr -d '/+=' | head -c 32)"
echo "JWT_ACCESS_SECRET=$(openssl rand -base64 64 | tr -d '/+=' | head -c 64)"
echo "JWT_REFRESH_SECRET=$(openssl rand -base64 64 | tr -d '/+=' | head -c 64)"
echo "LICENSE_SERVER_ADMIN_TOKEN=$(openssl rand -hex 32)"

Paste your license JWT into:

ECOMMUS_LICENSE_JWT=eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9....

Save + close.

Bring up the stack:

Terminal window
cd /opt/ecommus/repo
docker compose pull postgres redis verdaccio license-server
docker compose up -d postgres redis # data plane first
sleep 15
docker compose ps # postgres should show "healthy"
docker compose build license-server
docker compose up -d # everything else

Verify all containers are running:

Terminal window
docker compose ps
# All services should show "Up" and "(healthy)" where applicable

Health check the API:

Terminal window
curl -fsS http://127.0.0.1:4000/health
# {"ok":true,"service":"api","version":"1.0.0",...}

Step 5 — Caddy reverse proxy + HTTPS (5 min)

Section titled “Step 5 — Caddy reverse proxy + HTTPS (5 min)”

Render the Caddyfile from the template:

Terminal window
sudo cp /opt/ecommus/repo/infra/Caddyfile.template /etc/caddy/Caddyfile
sudo sed -i "s/{{DOMAIN}}/$ECOMMUS_DOMAIN/g; s/{{ADMIN_EMAIL}}/$ECOMMUS_ADMIN_EMAIL/g" \
/etc/caddy/Caddyfile
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
# Watch certificates being issued
sudo journalctl -u caddy -f
# Wait until you see "certificate obtained successfully" for each subdomain

External verification:

Terminal window
# From your laptop
curl -I https://api.$ECOMMUS_DOMAIN/health
# HTTP/2 200 + valid TLS

Open in your browser:

https://admin.myshop.com/login

Default operator account is in your email (or generated by the install script — check docker compose logs api | grep "default admin"). Log in.

You should land on the admin dashboard. The page header shows your tier + niche package(s).

In the Stripe dashboard:

  1. Webhooks → Add endpoint → URL: https://api.myshop.com/api/webhooks/stripe
  2. Select events: checkout.session.completed, payment_intent.succeeded, payment_intent.payment_failed
  3. Copy the Signing secret — paste into STRIPE_WEBHOOK_SECRET in .env
  4. Copy your Secret key + Publishable key from API keys page — paste into STRIPE_SECRET_KEY + STRIPE_PUBLISHABLE_KEY (the Stripe-canonical name; older docs may reference STRIPE_PUBLIC_KEY — that’s a typo, code reads STRIPE_PUBLISHABLE_KEY)
  5. Restart the api container so it picks up the new env:
Terminal window
docker compose restart api

Test from Stripe dashboard’s “Send test webhook” → expect 200 OK.

Step 8 — wire Resend (or your SMTP) (5 min)

Section titled “Step 8 — wire Resend (or your SMTP) (5 min)”

Resend (recommended):

  1. Sign up at resend.com, free tier
  2. Domains → Add Domainmyshop.com
  3. Add the 3 DNS records (DKIM × 2 + SPF) at your DNS provider
  4. Wait ~10 min, click “Verify” — should turn green
  5. API Keys → Create API Keyecommus-prod
  6. Paste into RESEND_API_KEY in .env, restart api:
Terminal window
docker compose restart api

Test by triggering a password reset on your admin account — email should arrive in seconds.

If you prefer SMTP (your existing email host), replace RESEND_API_KEY with SMTP_HOST + SMTP_PORT + SMTP_USER + SMTP_PASS in .env. See Environment Variables for the full list.

Terminal window
# On the VPS
cd /opt/ecommus/repo
node --env-file=/opt/ecommus/.env scripts/full-buying-flow-e2e.mjs

Expected output:

✅ FULL BUYING FLOW E2E: GREEN
- Install: t1-buyflow-1234567890
- Subscription: sub_test_1234567890
- Events: payment_failed, cancelled, email_resent, renewed, issued
- Mails: 3 (delivery + renewal + resend)

If it fails, the failing step name tells you exactly what to fix. Most common: Stripe webhook secret typo, or Resend domain not yet verified.

Daily Postgres dump (7-day retention):

Terminal window
# As ecommus user
sudo -u ecommus mkdir -p /opt/ecommus/backups
crontab -e -u ecommus
# Add this line:
0 3 * * * cd /opt/ecommus/repo && docker compose exec -T postgres pg_dumpall -U ecommus | gzip > /opt/ecommus/backups/pg_$(date +\%F).sql.gz && find /opt/ecommus/backups -name 'pg_*.sql.gz' -mtime +7 -delete

For off-server backup (recommended once you have real customers), pipe the dump to S3 / Backblaze B2 / Google Drive via rclone. The server-local dump is your “oh no I deleted a tenant” lifeline; the off-server copy is your “the datacenter is on fire” lifeline.

If full-buying-flow-e2e.mjs is green and admin login works:

  • Your install is production-ready for a single tenant
  • Your license is active until the date shown in the admin header
  • The buying-flow + anti-piracy + ops + renewal loops are all live

Next steps:

  • Read Choose your niche package below
  • For ongoing operations, bookmark the super-admin panel at https://superadmin.myshop.com
  • Subscribe to the release notes so you don’t miss security updates

The four canonical niche packages, what each ships with by default, and what’s included on each tier:

PackageDefault themeDefault plugins (community+)Optional add-ons (pro+)
servicestheme-servicesniche-booking, efactura-ro, notifications-smsniche-healthcare, niche-fitness
real-estatetheme-realestateniche-real-estate, efactura-romaps-integration, lead-management
traveltheme-travelniche-hotel, niche-booking, efactura-ropayment-stripe-deposits
ecommercetheme-fashionniche-ecommerce, efactura-ro, shipping-samedaymarketplace-emag, marketplace-amazon, payment-netopia

Switching package after install requires a license re-issue from your operator — talk to them before doing it; it’s not free.

SymptomMost likely causeFix
caddy reload fails with “tls handshake error”DNS hasn’t propagated, or Cloudflare proxy is still on (orange cloud)dig to verify; set Cloudflare records to DNS-only
docker compose up fails on postgres healthcheckwrong POSTGRES_PASSWORD between .env and existing data volumerotate the env value (preferred) OR docker volume rm ecommus_postgres_data (DESTRUCTIVE — only on a fresh install)
Stripe webhook returns 400 invalid_signaturewrong STRIPE_WEBHOOK_SECRET for that endpointeach Stripe endpoint has its own secret; copy the one for THIS endpoint
Resend emails go to spamdomain not verified / no DKIMfinish the DKIM step, wait an hour for inbox-provider reputation to update
npm install fails with 401 Unauthorized on @ecommus/...wrong / expired NPM tokencheck the email; tokens are tier-bound — pro-tier token can’t pull enterprise plugins
Browser shows “self-signed cert” warningCaddy fell back to staging because real ACME failedsudo journalctl -u caddy -n 100 — look for the actual ACME error
  • Operator support email (in your purchase confirmation) — for license / billing / account issues
  • GitHub Discussions at github.com/MDLABS-cmd/ecommus/discussions — for install + plugin questions
  • Status page at status.ecommus.ro — when license-server / npm.ecommus.ro is having a bad day

When asking for help, always include:

  • Your install_id (from cat /opt/ecommus/.env | grep INSTALL_ID)
  • The output of the failing command (full, no edits)
  • docker compose ps (which containers are up)
  • Last 50 lines of relevant logs (docker compose logs --tail 50 api)