Skip to main content

Self-hosting

Sokisoko ships as one Go API, one background worker, one PostgreSQL database and three web front-ends (admin console, vendor portal, storefront) that all talk to the same API. Docker Compose runs the backend; a reverse proxy (Caddy in the reference deployment) serves the front-ends and routes each tenant's domain.

What runs

ServiceRole
postgresPostgreSQL 16 with pgvector — data, the job queue and full-text search all live here
migrateOne-shot: applies the embedded SQL migrations idempotently, then exits
seedOptional one-shot: creates the platform superadmin (and demo data when SEED_DEMO is on)
apiThe HTTP API on :8080
workerBackground jobs — email, invoice PDFs, ERP sweeps, automations, digests
gotenbergRenders invoice PDFs; without it the API uses a stub renderer

Uploaded media (product photos, logos, renditions) lives on the mediadata volume at MEDIA_ROOT. In a multi-node deployment that volume must be shared.

First start

  1. Create a .env next to docker-compose.yml with at least POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB, JWT_SECRET (32+ random bytes — openssl rand -base64 32) and, for production, CONFIG_ENCRYPTION_KEY, SUPERADMIN_EMAIL and SUPERADMIN_PASSWORD. See Configuration for everything else. Never commit .env.
  2. Bring the stack up:
docker compose up -d --build
  1. Check health:
curl -s localhost:8080/healthz   # liveness
curl -s localhost:8080/readyz # readiness — pings the database
  1. Sign in to the admin console with the superadmin email and password you set. In development the seed creates admin@demo.test / admin1234 instead; production refuses to start with it.

Production guards

When ENV=production, the API validates its configuration at boot and exits rather than run unsafely:

  • SUPERADMIN_PASSWORD must be at least 12 characters and not a default such as admin1234, password or changeme.
  • CONFIG_ENCRYPTION_KEY must be set and distinct from JWT_SECRET — it seals tenant credentials (payment keys, ERP secrets, mailboxes) at rest.
  • JWT_SECRET must be at least 32 characters.
  • SEED_DEMO must be off — it creates demo logins.

Tenant domains

One installation hosts many organizations. Each storefront website has a domain (Settings → Websites, plus verified custom domains), and every storefront request is bound to a website by the host it arrives on — there is no fallback. Requests on an unknown host get "No store is configured for this address". The admin console is served under /console/ and the vendor portal under /portal/ on the same tenant host; the platform's own domain is PLATFORM_BASE_DOMAIN.

In the reference Caddy configuration every tenant host proxies /media/* to the API, serves the two SPAs from static builds, and proxies everything else to the storefront's server-side renderer, with on-demand TLS. If your proxy sits in front of the API on a different host, forward the store's host in X-Forwarded-Host (or X-Tenant-Host) so tenant resolution still works — see Authentication.

Email, PDFs and integrations

  • Email sends through Mailgun when MAILGUN_API_KEY and MAILGUN_DOMAIN are set, otherwise SMTP when SMTP_HOST is set, otherwise it is logged. Tenants can bring their own mailbox or Mailgun account from Settings → Configuration.
  • PDFs need GOTENBERG_URL; the Compose file wires it.
  • ERP providers that use OAuth (QuickBooks Online, Xero, Business Central) need platform app credentials in the environment before the admin can connect them; SAP, SAP Business One, Odoo and the custom webhook need nothing platform-side. See ERP and accounting sync.
  • The agent API (OAuth 2.1 + MCP) is mounted only when API_PUBLIC_URL is set. See AI agents and MCP.

Upgrading

Pull the new images or source, then docker compose up -d --build. The migrate service applies any new migrations before the API starts; migrations are forward-only. Keep a database backup routine outside the containers.