Prerequisites
If you have do not have a running NATS server configured for Trellis, please follow the Prepare NATS guide first.
What you need
podmansystemctl --userwith Quadlet support- the
trellisCLI installed (see Install the Trellis CLI) - an OAuth application (GitHub) for your environment
1. Setup the workspace
If you are continuing from the Prepare NATS guide, then use the same trellis workspace as before:
cd trellis Otherwise, create a working directory:
mkdir trellis
cd trellis Create the directory structure for config and secrets:
mkdir -p config/secrets
chmod 700 config/secrets Download config.example.jsonc and save it as ./config/config.jsonc.
2. Generate the Trellis session key
trellis keygen --out ./keys/trellis.seed --pubout ./keys/trellis.pub Only the public key is shared with Trellis APIs. Keep ./keys/trellis.seed private.
3. Collect the NATS values
The runtime needs several values from the NATS setup. If you still have them in your shell from the NATS guide, you can skip the extraction steps.
Get the AUTH account public key:
NATS_AUTH_ISSUER_NKEY=$(./nsc.sh describe account -n AUTH --field sub | tr -d '"') Get the TRELLIS account public key:
NATS_AUTH_TARGET_NKEY=$(./nsc.sh describe account -n TRELLIS --field sub | tr -d '"') Get the auth callout XKey public key. The seed is in the nkeys directory:
XKEY_PUB=$(./nsc.sh describe account -n AUTH -J | grep '"xkey"' | tr -d ' ",' | cut -d: -f2) 4. Write secrets to disk
The runtime reads all private key material from files. Write them into ./config/secrets/:
cp ./keys/trellis.seed ./config/secrets/session-key.seed
cp ./keys/auth-issuer-signing.nk ./config/secrets/auth-issuer-signing.seed
cp ./keys/trellis-target-signing.nk ./config/secrets/auth-target-signing.seed
cat ./nkeys/keys/X/${XKEY_PUB:1:2}/${XKEY_PUB}.nk > ./config/secrets/auth-sx.seed
echo -n "$GITHUB_CLIENT_SECRET" > ./config/secrets/github-client-secret
chmod 600 ./config/secrets/* The entire ./config/ directory is mounted read-only into the container as /etc/trellis/. All *File paths in the config are container paths under that mount point.
5. Configure the auth runtime
Edit ./config/config.jsonc and fill in your deployment values:
{
"nats": {
"servers": "trellis-nats",
"trellis": {
"credsPath": "/run/trellis/creds/trellis-trellis.creds"
},
"auth": {
"credsPath": "/run/trellis/creds/auth-trellis.creds"
},
"sentinelCredsPath": "/run/trellis/creds/sentinel.creds",
"authCallout": {
"issuer": {
"nkey": "<value from step 3>",
"signingSeedFile": "/etc/trellis/secrets/auth-issuer-signing.seed"
},
"target": {
"nkey": "<value from step 3>",
"signingSeedFile": "/etc/trellis/secrets/auth-target-signing.seed"
},
"sxSeedFile": "/etc/trellis/secrets/auth-sx.seed"
}
},
"sessionKeySeedFile": "/etc/trellis/secrets/session-key.seed",
"client": {
"natsServers": ["ws://host.containers.internal:8080"]
},
"oauth": {
"redirectBase": "http://localhost:3000/auth/callback",
"alwaysShowProviderChooser": true,
"providers": {
"github": {
"type": "github",
"clientId": "<your GitHub OAuth app client ID>",
"clientSecretFile": "/etc/trellis/secrets/github-client-secret"
}
}
}
} client.natsServers is the browser-facing websocket endpoint list returned by the auth service during bind. This lets the console discover the correct Trellis realtime endpoint from the instance URL it signed into.
Create ./config/trellis.env with a single line pointing the runtime at the config file:
TRELLIS_CONFIG=/etc/trellis/config.jsonc Use localhost consistently across the runtime, browser app, and OAuth app config.
6. Bootstrap NATS buckets
Create the JetStream streams and KV buckets that Trellis requires:
trellis bootstrap nats \
--servers 127.0.0.1 \
--trellis-creds ./nkeys/creds/Trellis/TRELLIS/trellis.creds \
--auth-creds ./nkeys/creds/Trellis/AUTH/trellis.creds 7. Deploy the runtime with Quadlet
The Trellis repo provides Quadlet templates at deploy/quadlet/. Use those as a starting point.
Write ~/.config/containers/systemd/trellis.container:
[Unit]
Description=Trellis runtime
After=trellis-nats.service
Requires=trellis-nats.service
[Container]
ContainerName=trellis
Image=ghcr.io/qlever-llc/trellis:latest
PublishPort=127.0.0.1:3000:3000
EnvironmentFile=%h/trellis/config/trellis.env
Volume=%h/trellis/config:/etc/trellis:ro,Z
Volume=%h/trellis/nkeys/creds/Trellis/AUTH/trellis.creds:/run/trellis/creds/auth-trellis.creds:ro,Z
Volume=%h/trellis/nkeys/creds/Trellis/AUTH/sentinel.creds:/run/trellis/creds/sentinel.creds:ro,Z
Volume=%h/trellis/nkeys/creds/Trellis/TRELLIS/trellis.creds:/run/trellis/creds/trellis-trellis.creds:ro,Z
Network=trellis.network
UserNS=keep-id
NoNewPrivileges=true
DropCapability=all
ReadOnly=true
Tmpfs=/tmp:rw,noexec,nosuid,nodev,size=16m
[Service]
Restart=on-failure
RestartSec=5
[Install]
WantedBy=default.target Write ~/.config/containers/systemd/console.container:
[Unit]
Description=Trellis console
After=trellis.service
[Container]
ContainerName=console
Image=ghcr.io/qlever-llc/console:latest
PublishPort=127.0.0.1:5173:80
Environment=VITE_TRELLIS_AUTH_URL=http://host.containers.internal:3000
Environment=VITE_TRELLIS_NATS_SERVERS=ws://host.containers.internal:8080
Network=trellis.network
UserNS=keep-id
NoNewPrivileges=true
DropCapability=all
ReadOnly=true
Tmpfs=/var/cache/nginx
Tmpfs=/var/run
[Service]
Restart=on-failure
RestartSec=5
[Install]
WantedBy=default.target The runtime mounts ./config/ as /etc/trellis/ read-only, providing both the config file and secrets. NATS creds are mounted individually from ./nkeys/creds/ to the container paths referenced by the config. The app container reads its public browser config from runtime environment variables.
If you want the user service to keep running after logout, enable linger once:
loginctl enable-linger "$USER" 8. Start the services
systemctl --user daemon-reload
systemctl --user start trellis.service
systemctl --user start console.service Test the auth endpoint:
curl -i http://localhost:3000/auth/login/github An HTTP 400 is expected before signed login parameters are sent — it confirms the runtime is serving.
9. Create the first admin
Before logging into the console, bootstrap the GitHub account that will become the first administrator:
trellis bootstrap admin \
--servers 127.0.0.1 \
--creds ./nkeys/creds/Trellis/AUTH/trellis.creds \
--origin github \
--id github:YOUR_GITHUB_USER_ID This seeds the user projection with the default console access set: admin, trellis.catalog.read, and trellis.contract.read.
10. Log in with the CLI
trellis --nats-servers 127.0.0.1 auth login \
--auth-url http://localhost:3000 \
--provider github Check the session:
trellis auth status 11. Verify
Check that all services are responding:
curl http://127.0.0.1:8222/healthz
curl -i http://localhost:3000/auth/login/github
curl -I http://localhost:5173/
trellis auth status Then open http://localhost:5173 in your browser and complete the OAuth login flow with that same GitHub account. You should land on a logged-in console session.