Self Hosting
Run your own OpenShock server stack with Docker Compose, Postgres and a Redis-compatible cache.
Requirements
Hardware:
- A server / computer to run linux containers on. Docker Desktop with WSL also works, but isn't really recommended.
Software:
dockeranddocker composeinstalled on the server. You can use the Docker install script for linux, or Docker Desktop on Windows.
Alternatively podman or podman desktop with compose addon.
Other:
- A domain or subdomain is recommended.
- HTTPS - required for cookies to work securely. You can use cloudflare for lets encrypt for example.
Preparing the server
Install software from the Requirements on the server.
Docker compose setup
Make a new folder in a known location.
Add two files with the names docker-compose.yml and .env. Paste their contents from below.
These two can also be found in the API repository
Reverse proxy
By default the reverse proxy that comes with this example is traefik. Everything should be setup and should be available under https on port 443 on your domain if done correctly.
Done
Congratulations, the backend and website should be working now. 🥳
You can now set the backend domain for the firmware to your api url via the domain serial command.
Migrating from the old docker compose setup
If you are running the previous subdomain-based stack (api.example.com /
gateway.example.com plus the webui container), read this before you pull.
Back up first
Back up your postgres-data folder before starting. The Postgres upgrade and the volume path
change below both touch persistent data.
What changed
| Old | New |
|---|---|
Subdomains: api., gateway., UI on the root | One host, one port, split by path: /api, /gateway, UI on / |
webui container (ghcr.io/openshock/webui, port 80) | frontend container (ghcr.io/openshock/frontend, port 3000) |
redis/redis-stack-server, keyspace events KEA | DragonflyDB, keyspace events Ex |
postgres:17, volume at /var/lib/postgresql/data | postgres:18, volume at /var/lib/postgresql |
.env holds raw OPENSHOCK__* variables | .env holds short knobs, the compose file maps them to OPENSHOCK__* |
Images pinned to latest | OPENSHOCK_TAG / OPENSHOCK_FRONTEND_TAG |
Traefik redirectregex rules for /s/, /c/, /t/ | Handled by the frontend itself (/c/ is now /usc/) |
1. Postgres 17 → 18
Postgres 18 will not start on a 17 data directory, and the mount point moved (PG18 keeps
its data in postgres-data/18/docker). Dump on the old stack first:
docker compose exec db pg_dump -U openshock -d openshock -Fc > openshock.dump
docker compose down
mv postgres-data postgres-data-17-backupThen start the new stack's database and restore into it:
docker compose up -d db
docker compose exec -T db pg_restore -U openshock -d openshock < openshock.dump2. Redis → Dragonfly
Nothing to migrate — only cache and ephemeral state lived there. Delete ./redis-data
once the new stack is running. If you keep your own Redis instead of Dragonfly, set its
keyspace events to at least Ex.
3. Check your database name
The old compose file built the connection string with Database=${PG_USER} while it
created POSTGRES_DB=${PG_DB}, so the database in use was actually named after the
user. This is fixed now. If you had set PG_USER and PG_DB to different values,
your data lives in the database named after PG_USER — set PG_DB to that name, or
restore your dump into PG_DB.
4. Rewrite your .env
Copy the new .env from above and port your values across:
Old (.env) | New (.env) |
|---|---|
OPENSHOCK_DOMAIN | OPENSHOCK_HOST |
OPENSHOCK_API_SUBDOMAIN | OPENSHOCK_API_PATH (default /api) |
OPENSHOCK_GATEWAY_SUBDOMAIN | OPENSHOCK_GATEWAY_PATH (default /gateway) |
OPENSHOCK__MAIL__TYPE=SMTP | MAIL_TYPE=Smtp — or Mailjet, or None for no outbound mail |
OPENSHOCK__MAIL__SENDER__NAME / __EMAIL | MAIL_SENDER_NAME / MAIL_SENDER_EMAIL |
OPENSHOCK__MAIL__SMTP__* | SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, SMTP_ENABLESSL, SMTP_VERIFYCERTIFICATE |
OPENSHOCK__MAIL__MAILJET__KEY / __SECRET | MAILJET_KEY / MAILJET_SECRET |
OPENSHOCK__MAIL__MAILJET__TEMPLATE__PASSWORDRESET | Gone — mail templates are generated now |
OPENSHOCK__TURNSTILE__ENABLE | OPENSHOCK_TURNSTILE_ENABLE |
OPENSHOCK__ACCOUNT__REGISTRATIONENABLED | OPENSHOCK_REGISTRATION_ENABLED |
OPENSHOCK__LCG__COUNTRYCODE | OPENSHOCK_LCG_COUNTRYCODE |
PG_PASS, PG_USER and PG_DB keep their names.
Extra variables no longer pass through
The old api service had env_file: .env, so any extra OPENSHOCK__* variable you dropped into
.env reached the container automatically. It does not anymore. Anything not in the table above
has to be added explicitly to the x-openshock-env block (shared by api, cron and lcg) or to the
individual service.
New knobs worth knowing:
OPENSHOCK_PORT— serve the stack on a non-443 port. Leave blank for 443.OPENSHOCK_TAG/OPENSHOCK_FRONTEND_TAG— pin images instead of trackinglatest. The frontend is a separate repository and versions independently of the backend.OPENSHOCK_FRONTEND_TLS_INSECURE— skip backend certificate verification from the frontend. For testing with self-signed certs only, never in production.
5. DNS and certificates
Only one hostname is needed now, so you can drop the api. and gateway. DNS records
and use a single-name certificate instead of a wildcard. Keep the old records pointed at
the server until every client has moved over.
6. Clients and firmware
The gateway now advertises host, port and path prefix (OPENSHOCK__LCG__PUBLICPORT,
OPENSHOCK__LCG__PUBLICPATH) rather than a bare FQDN, and devices pick that up from the
API when they reconnect. Check that your firmware and client versions understand a
gateway path prefix before cutting over — in this single-host layout you cannot avoid it
by blanking OPENSHOCK_GATEWAY_PATH, because the frontend owns the root and the gateway
needs a prefix of its own to be routable.
7. Bring it up
docker compose pull
docker compose up -dThe UI is on https://<host>/, the API docs on https://<host>/api/scalar/viewer and
Hangfire on https://<host>/hangfire.
Using your own reverse proxy? Remove the traefik service and route by path: / →
frontend:3000, OPENSHOCK_API_PATH → api:80, OPENSHOCK_GATEWAY_PATH → lcg:80,
/hangfire → cron:780. Do not strip the prefixes — each app serves under its own
UsePathBase.
Last updated on