Skip to content

Self-host 60-second tour

Before you set aside an afternoon for the full install, take sixty seconds to see what self-hosting Polaris Express actually looks like. This page is not a tutorial — it is a tour. It shows you the shape of the system, what you will run, and what you will need on hand. When you are ready to install for real, jump to the prerequisites page.

Polaris Express is five components. Three of them run on infrastructure you control:

  • Directoryexpresscharge/
    • Directoryweb/ Deno + Fresh fullstack app (admin + customer UI, API, SSE)
    • Directorysteve/ SteVe OCPP backend — your ChargeBoxes connect here
    • Directoryemail-worker/ Cloudflare Worker for transactional email
    • Directoryios/ Native iOS app (you do not host this)
    • Directorydocs/ This site (you do not host this)

The web app, a Postgres database, and a sync worker run together as one Docker Compose stack. SteVe runs as its own stack — your chargers talk to it over WebSocket. The email worker is deployed to Cloudflare (it is not in the Compose stack).

The root docker-compose.yml brings up four services:

ServiceWhat it does
postgresPostgres 17, holds all application state
migrateOne-shot — runs deno task db:migrate then exits
appThe Fresh web app, exposes port 8000
syncBackground worker that reconciles SteVe ↔ web ↔ Lago

Once it is up, you curl localhost:8000/healthz and you are looking at your own copy of Polaris Express.

  1. Clone the monorepo

    Terminal window
    git clone --recursive [email protected]:expresscharge/monorepo.git expresscharge
    cd expresscharge

    The --recursive flag pulls all five submodules. If you forget it, make bootstrap will fix it.

  2. Fill in web/.env

    The whole stack reads web/.env. You will set database credentials, the SteVe WebSocket URL, BetterAuth secrets, and (optionally) Lago and Cloudflare email-worker URLs. The full reference is on the environment variables page.

  3. Bring it up

    Terminal window
    make up

    This runs docker compose up against the root docker-compose.yml. First boot pulls images, builds web/ from its Dockerfile, runs migrations, and starts the app and sync worker.

  4. Stand up SteVe separately

    SteVe is its own Docker stack inside steve/. It needs its own Postgres and its own config. Your chargers will point at SteVe’s WebSocket endpoint; the web app will point at SteVe’s REST API.

  5. Deploy the email worker

    From email-worker/, run npx wrangler deploy. Then set CF_EMAIL_WORKER_URL in web/.env to the deployed worker URL. Without this, magic link emails will not send.

What you need on hand before you start the real install

Section titled “What you need on hand before you start the real install”
  • A Linux host with Docker and Docker Compose. 4 GB RAM is enough for a small site.
  • A domain name and the ability to set DNS records. The web app, the SteVe WebSocket, and the email worker each need their own hostname.
  • A TLS terminator in front of the stack (Caddy, Traefik, or a cloud load balancer). The Compose stack exposes plain HTTP on :8000.
  • A Cloudflare account for the email worker, plus an email-sending provider (Resend, Postmark, or SES) that the worker will call.
  • Optional — a Lago instance if you want metered billing. Polaris Express works without it; you just lose the billing surface.

This page does not install anything. To check that you can at least clone the repo and that Docker is healthy:

Terminal window
git clone --recursive [email protected]:expresscharge/monorepo.git /tmp/expre-tour
cd /tmp/expre-tour && docker compose config

docker compose config prints the resolved Compose file. If it errors on a missing web/.env, that is expected — you have not filled it in yet. If it errors on Docker itself, fix that before proceeding.

  • “I do not want to run SteVe.” You have to. SteVe is the OCPP endpoint your chargers connect to. There is no hosted alternative.
  • “Can I skip the email worker?” Only if you are happy with no user signups, no magic link logins, and no transactional email. For any real deployment, no.
  • “Can I skip Lago?” Yes. Leave the Lago env vars unset and the billing surface will be inert. Sessions and charging still work.

Ready to do it for real? Start with prerequisites — the checklist of accounts, DNS records, and host requirements you will want lined up before the first make up.