Keeyo KEEYOEquipment register

Form K-00 · Installation

Install

One container, one volume, one port. All data lives in a single SQLite file.

Docker (recommended)

docker run -d --name keeyo \
  -p 5390:5390 \
  -v keeyo-data:/data \
  --restart unless-stopped \
  ghcr.io/ans-ib/keeyo:latest

Docker Compose

services:
  keeyo:
    image: ghcr.io/ans-ib/keeyo:latest
    container_name: keeyo
    restart: unless-stopped
    ports:
      - "5390:5390"
    volumes:
      - keeyo-data:/data

volumes:
  keeyo-data:

Then open http://localhost:5390. Your first visit creates the admin account — there is no default password and no open registration.

Build from source

git clone https://github.com/ans-ib/keeyo.git && cd keeyo
docker compose up -d

Without Docker

Needs Node.js 22.13+ — Keeyo uses Node's built-in SQLite, so there are no native modules to compile.

git clone https://github.com/ans-ib/keeyo.git && cd keeyo
npm install
npm start          # http://localhost:5390, data in ./data

Configuration

VariableDefaultWhat it does
PORT5390Web server port.
DATA_DIR/dataWhere the SQLite database lives.
SESSION_TTL_DAYS30How long sign-ins last.
REGISTRY_REFRESH_DAYS7How often the FIDO device registry is re-fetched.
KEEYO_OFFLINEunsetSet 1 to block all outbound requests (registry updates off).
TRUST_PROXYunsetSet 1 only behind a reverse proxy so X-Forwarded-* headers are honored.
KEEYO_DISABLE_MFAunsetSet 1 to skip every sign-in second factor — security keys and authenticator codes (lockout recovery).
KEEYO_OIDC_ISSUERunsetOIDC issuer URL — setting this (plus client id/secret) enables single sign-on. See below.
KEEYO_OIDC_CLIENT_IDunsetOIDC client id.
KEEYO_OIDC_CLIENT_SECRETunsetOIDC client secret. Leave empty for public clients (PKCE-only) — Cosmos's built-in OpenID works this way.
KEEYO_OIDC_ADMIN_BOOTSTRAPunsetSet 1 on a fresh instance to skip the setup screen: the first person to sign in through SSO becomes the admin, and password setup is refused. Closes the first-visit race behind platforms like Cosmos.
KEEYO_OIDC_NAMESSOLabel on the sign-in button (e.g. Authentik).
KEEYO_OIDC_AUTO_CREATE1Set 0 to refuse SSO sign-ins for usernames that don't already exist in Keeyo.
KEEYO_OIDC_SCOPESopenid profile emailOIDC scopes to request.
KEEYO_OIDC_USERNAME_CLAIMpreferred_usernameID-token claim used as the Keeyo username.
KEEYO_OIDC_LOGOUT_URLunsetWhere SSO users are sent after signing out of Keeyo.
KEEYO_OIDC_REQUIRE_VERIFIEDunsetSet 1 to require email_verified before auto-creating an account.
KEEYO_OIDC_DISABLE_PASSWORDunsetSet 1 to turn off password sign-in for everyone except admins.
KEEYO_SMTP_HOSTunsetSMTP server — setting this (plus FROM) enables email and password-reset links.
KEEYO_SMTP_PORT587/465SMTP port (default depends on security mode).
KEEYO_SMTP_SECURITYstarttlstls, starttls or none.
KEEYO_SMTP_USER / KEEYO_SMTP_PASSunsetSMTP credentials, if the server needs them.
KEEYO_SMTP_FROMunsetSender address for outgoing mail.
KEEYO_SMTP_FROM_NAMEKeeyoSender display name.

Single sign-on (Authentik, Authelia, Keycloak, …)

Keeyo speaks standard OpenID Connect (authorization code flow with PKCE), so any self-hosted identity provider works. The easiest way is the admin panel: Settings → Single sign-on covers everything — provider name, client credentials, issuer (endpoints are auto-discovered; manual Auth/Token URL overrides exist for providers without discovery), scopes, the identifier claim, a logout URL, plus toggles for auto-creating users, requiring a verified email, and disabling password login (admins always keep password sign-in as the lockout hatch). Environment variables do the same headlessly and, when set, lock the panel. For Authentik:

  1. Create an OAuth2/OpenID provider (confidential client, authorization code flow) with redirect URI https://keys.example.com/api/oidc/callback, and an application using it.
  2. Set the environment variables: KEEYO_OIDC_ISSUER to the provider's OpenID issuer URL (e.g. https://auth.example.com/application/o/keeyo/), plus KEEYO_OIDC_CLIENT_ID, KEEYO_OIDC_CLIENT_SECRET, and KEEYO_OIDC_NAME=Authentik.
  3. Restart Keeyo — a Continue with Authentik button appears on the sign-in page.

SSO users are matched by username (preferred_username, falling back to the email's local part) and created automatically on first sign-in as regular users — admins are only ever appointed locally. SSO accounts have no Keeyo password, and Keeyo's own second factor is skipped for them: your identity provider owns their authentication, including MFA. Password sign-in keeps working alongside SSO for local accounts.

Email & password reset

Configure SMTP in Settings → Email (or via the KEEYO_SMTP_* variables, which lock the panel) and a Forgot password? link appears on the sign-in page. Users set their address under Settings → Account; reset links are single-use, expire after 45 minutes, and revoke every session of the account when used. Accounts created by SSO have no password and are excluded. The SMTP client is built in — no relay container needed — and speaks TLS, STARTTLS or plain.

Reverse proxy & HTTPS

Keeyo speaks plain HTTP; put your reverse proxy in front for TLS and set TRUST_PROXY=1. Caddy example:

keys.example.com {
    reverse_proxy keeyo:5390
}
⚠HTTPS matters more than usual here. The WebAuthn features — key scanning, identification, tap-to-reveal, security-key login — only work from localhost or an HTTPS origin. On a plain-HTTP LAN IP they politely disable themselves. Browsers also refuse WebAuthn on raw IP addresses, so use a hostname.

Backups

Locked out?

On the server (or in the container):

docker exec -it keeyo node scripts/reset-password.js <username> <new-password>

This resets the password, signs out all sessions and removes every second factor on the account — sign-in security keys, the authenticator app, and recovery codes. Alternatively, start Keeyo once with KEEYO_DISABLE_MFA=1 to skip the second factor.