Skip to content

Linux + SQLite

Run oidc-exchange on a single Linux host using SQLite for all persistent storage. No external database services needed. This is the simplest production deployment: one binary, one config file, one database file.

Optionally, use LMDB for session storage when you want faster session lookups without adding a network service.

A runnable example is in examples/linux-sqlite/.

  • Single-server deployment with no external database dependencies
  • Low to moderate traffic (hundreds of requests per second)
  • You want the simplest possible ops story: back up one directory, restore anywhere
  • Optionally: use LMDB for faster session reads on the same server
┌──────────────┐
│ nginx │
│ (TLS term) │
└──────┬───────┘
┌──────┴───────┐
│oidc-exchange │
└──┬───────┬───┘
│ │
┌────────┴──┐ ┌─┴──────────┐
│ SQLite │ │ LMDB │
│ (users) │ │ (sessions) │
└────────────┘ └─────────────┘
optional

Without LMDB, SQLite handles both users and sessions.

  • A Linux server with oidc-exchange binary (see build instructions)
  • A writable directory for the SQLite database file
  • (Optional) A writable directory for the LMDB environment
Terminal window
sudo mkdir -p /var/lib/oidc-exchange/data
sudo mkdir -p /etc/oidc-exchange
sudo chown -R oidc-exchange:oidc-exchange /var/lib/oidc-exchange

If using LMDB for sessions:

Terminal window
sudo mkdir -p /var/lib/oidc-exchange/lmdb
sudo chown oidc-exchange:oidc-exchange /var/lib/oidc-exchange/lmdb
Terminal window
openssl genpkey -algorithm ed25519 -out /etc/oidc-exchange/signing-key.pem
chmod 600 /etc/oidc-exchange/signing-key.pem

Create /etc/oidc-exchange/config/production.toml:

[server]
host = "127.0.0.1"
port = 8080
issuer = "https://auth.example.com"
[key_manager]
adapter = "local"
[key_manager.local]
private_key_path = "/etc/oidc-exchange/signing-key.pem"
algorithm = "EdDSA"
kid = "prod-1"
[repository]
adapter = "sqlite"
[repository.sqlite]
path = "/var/lib/oidc-exchange/data/oidc-exchange.db"
[audit]
adapter = "stdout"
durability = "observe"
[telemetry]
enabled = true
exporter = "stdout"
[providers.google]
adapter = "oidc"
issuer = "https://accounts.google.com"
client_id = "${GOOGLE_CLIENT_ID}"
client_secret = "${GOOGLE_CLIENT_SECRET}"
scopes = ["openid", "email", "profile"]
# Origins Google's discovery document may name beyond the issuer's origin:
endpoint_origins = ["https://oauth2.googleapis.com", "https://www.googleapis.com"]

endpoint_origins pins which origins a provider’s discovery document is allowed to name; each entry must be a bare https://host[:port], and an unpinned origin logs a warning when discovered (see Identity Providers).

SQLite runs with WAL journal mode and foreign keys enabled automatically. The database file and tables are created on first startup.

LMDB is an embedded key-value store optimized for read-heavy workloads. Session lookups (every token refresh) are the hottest path in oidc-exchange, and LMDB serves these from memory-mapped files with zero-copy reads.

[server]
host = "127.0.0.1"
port = 8080
issuer = "https://auth.example.com"
[key_manager]
adapter = "local"
[key_manager.local]
private_key_path = "/etc/oidc-exchange/signing-key.pem"
algorithm = "EdDSA"
kid = "prod-1"
# Users in SQLite
[repository]
adapter = "sqlite"
[repository.sqlite]
path = "/var/lib/oidc-exchange/data/oidc-exchange.db"
# Sessions in LMDB
[session_repository]
adapter = "lmdb"
[session_repository.lmdb]
path = "/var/lib/oidc-exchange/lmdb"
max_size_mb = 256
[audit]
adapter = "stdout"
durability = "observe"
[telemetry]
enabled = true
exporter = "stdout"
[providers.google]
adapter = "oidc"
issuer = "https://accounts.google.com"
client_id = "${GOOGLE_CLIENT_ID}"
client_secret = "${GOOGLE_CLIENT_SECRET}"
scopes = ["openid", "email", "profile"]

The max_size_mb setting controls the LMDB memory map size. 256 MB is generous for session data: each session is roughly 500 bytes, so 256 MB supports ~500,000 concurrent sessions. The space is reserved but not allocated until used.

Terminal window
cat > /etc/oidc-exchange/env <<'EOF'
GOOGLE_CLIENT_ID=your-client-id
GOOGLE_CLIENT_SECRET=your-client-secret
EOF
chmod 600 /etc/oidc-exchange/env
/etc/systemd/system/oidc-exchange.service
[Unit]
Description=oidc-exchange token service
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=oidc-exchange
Group=oidc-exchange
ExecStart=/usr/local/bin/oidc-exchange
WorkingDirectory=/etc/oidc-exchange
Restart=on-failure
RestartSec=5
EnvironmentFile=/etc/oidc-exchange/env
Environment=OIDC_EXCHANGE_ENV=production
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadOnlyPaths=/etc/oidc-exchange
ReadWritePaths=/var/lib/oidc-exchange
PrivateTmp=true
[Install]
WantedBy=multi-user.target

Note ReadWritePaths=/var/lib/oidc-exchange: the service needs write access to the SQLite and LMDB data directories.

Terminal window
sudo cp target/release/oidc-exchange /usr/local/bin/
sudo useradd --system --no-create-home oidc-exchange
sudo systemctl daemon-reload
sudo systemctl enable --now oidc-exchange

See the generic Linux server guide for the nginx configuration.

All state lives in /var/lib/oidc-exchange/. To back up:

Terminal window
# SQLite: use the .backup command for a consistent snapshot
sqlite3 /var/lib/oidc-exchange/data/oidc-exchange.db ".backup /tmp/oidc-exchange-backup.db"

For LMDB, copy the directory while the service is running; LMDB’s copy-on-write design means readers never block writers and file copies are crash-consistent. Alternatively, stop the service and copy /var/lib/oidc-exchange/lmdb/.

LMDB session data is ephemeral (sessions expire). Losing it only forces users to re-authenticate.

  • Single-server only: SQLite and LMDB do not support concurrent access from multiple processes on different hosts. For multi-server deployments, use PostgreSQL or DynamoDB.
  • Write throughput: SQLite with WAL handles hundreds of writes per second. If you need thousands, consider PostgreSQL.
  • No horizontal scaling: you cannot add more oidc-exchange instances behind a load balancer with SQLite/LMDB. Each instance would have its own database.