Install
Run lomiri with Docker Compose, with HTTPS set up for you.
lomiri ships as Docker images. The compose file below runs everything you need: Postgres, the API, the web UI and Caddy, which gets a TLS certificate for your domain automatically.
Requirements
- A Linux server with Docker and Docker Compose 2.23 or newer.
- A domain for the panel (for example
panel.example.com) whose DNS points at the server. - Ports 80 and 443 reachable from the internet, so Caddy can issue the certificate.
Download the compose file
mkdir lomiri && cd lomiri
curl -fsSLO https://lomiri.hyprr.space/install/docker-compose.yml
curl -fsSL https://lomiri.hyprr.space/install/env.example -o .envFill in .env
Set the panel's public URL and generate the two secrets:
sed -i "s#^APP_URL=.*#APP_URL=https://panel.example.com#" .env
sed -i "s/^POSTGRES_PASSWORD=.*/POSTGRES_PASSWORD=$(openssl rand -hex 24)/" .env
sed -i "s/^OAUTH_STATE_SECRET=.*/OAUTH_STATE_SECRET=$(openssl rand -hex 32)/" .envOr open .env in an editor. Every option is described in
Configuration.
Start lomiri
docker compose up -dThe first start pulls the images and creates the database. Later starts apply any database updates automatically.
Create your admin account
docker compose exec api bun run create:admin you@example.com 'a-strong-password' adminThe arguments are email, password and username.
Sign in
Open https://panel.example.com and sign in with the account you just created. From there,
connect your first node and create a server.
The files
# lomiri — Postgres, API, web UI and Caddy (automatic HTTPS) in one stack.
#
# 1. Put this file and .env (from env.example) in an empty directory, fill in .env
# 2. docker compose up -d
# 3. docker compose exec api bun run create:admin you@example.com 'a-strong-password' admin
#
# Docs: https://panel.hyprr.space/docs
services:
postgres:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_USER: lomiri
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
POSTGRES_DB: lomiri
volumes:
- postgres:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U lomiri -d lomiri"]
interval: 5s
timeout: 5s
retries: 10
# Runs database migrations on start, then serves the API. Raise scale to run more replicas; Caddy
# spreads requests across all of them.
api:
image: ghcr.io/hyprrspace/lomiri-api:${LOMIRI_TAG:-latest}
restart: unless-stopped
scale: 1
depends_on:
postgres:
condition: service_healthy
environment:
APP_URL: ${APP_URL:?set APP_URL in .env}
APP_NAME: ${APP_NAME:-}
DATABASE_URL: postgres://lomiri:${POSTGRES_PASSWORD}@postgres:5432/lomiri
OAUTH_STATE_SECRET: ${OAUTH_STATE_SECRET:?set OAUTH_STATE_SECRET in .env}
LOG_LEVEL: ${LOG_LEVEL:-info}
TRUSTED_PROXIES: ${TRUSTED_PROXIES:-}
web:
image: ghcr.io/hyprrspace/lomiri-web:${LOMIRI_TAG:-latest}
restart: unless-stopped
scale: 1
depends_on:
- api
environment:
LOMIRI_API_URL: http://api:3000
TRUSTED_PROXIES: ${TRUSTED_PROXIES:-}
# Terminates TLS for APP_URL (certificates are issued automatically) and routes /api/* to the API,
# everything else to the web UI. Remove it if you already run your own reverse proxy.
caddy:
image: caddy:2-alpine
restart: unless-stopped
depends_on:
- api
- web
ports:
- "80:80"
- "443:443"
- "443:443/udp"
environment:
APP_URL: ${APP_URL}
configs:
- source: caddyfile
target: /etc/caddy/Caddyfile
volumes:
- caddy-data:/data
- caddy-config:/config
configs:
caddyfile:
content: |
{$$APP_URL} {
encode zstd gzip
# "dynamic a" re-resolves the service name, so every replica gets traffic.
handle /api/* {
reverse_proxy {
dynamic a api 3000
}
}
handle {
reverse_proxy {
dynamic a web 3000
}
}
}
volumes:
postgres:
caddy-data:
caddy-config: