Server Setup

Starting Trellis

A running Trellis instance with an admin account ready for development.

Prerequisites

If you have do not have a running NATS server configured for Trellis, please follow the Prepare NATS guide first.

What you need

  • podman
  • systemctl --user with Quadlet support
  • the trellis CLI 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.