OOrchard

Deployment

This guide covers deploying Orchard in a production environment.

Preparation

Before deploying, ensure you have:

  1. A server meeting the system requirements
  2. A domain name pointing to your server
  3. TLS termination (a reverse proxy such as Caddy or nginx with Let's Encrypt)
  4. PostgreSQL running and accessible
  5. Fence built and ready to run — required for a production deployment, not optional

DNS Configuration

Create DNS records pointing to your server:

code
orchard.example.com.  A  203.0.113.10

If you want per-grove SSH subdomains, add a wildcard:

code
*.orchard.example.com.  A  203.0.113.10

TLS with a Reverse Proxy

The Orchard API server runs plain HTTP on port 8080. Use a reverse proxy to terminate TLS:

Caddy example (Caddyfile):

code
orchard.example.com {
  reverse_proxy localhost:8080
}

nginx example:

code
server {
  listen 443 ssl;
  server_name orchard.example.com;
 
  ssl_certificate     /etc/letsencrypt/live/orchard.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/orchard.example.com/privkey.pem;
 
  location / {
    proxy_pass http://localhost:8080;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
  }
}

Authentication (Fence)

The core API defaults to authentication disabled, which is only appropriate for local development. A production deployment always runs Fence — Orchard's own OAuth2/OIDC authorization server (the fence module) — alongside the core API, so you don't need to stand up a separate identity provider just to enable it. It's a separate process, published as a JAR on each release — run both:

code
curl -LO https://github.com/orchard-cde/orchard/releases/latest/download/fence.jar
java -jar fence.jar --server.port=7779

Fence needs a few settings pointed at your real deployment before it's production-ready:

Env varPurposeDefault
FENCE_ISSUERPublic URL Fence identifies itself as. Must match what you set as issuer-uri on the core API — the default (http://localhost:<port>) only works for local/single-host setups.http://localhost:7779
FENCE_SIGNING_KEY_PATHWhere Fence's JWT signing key is stored. Generated automatically on first boot if the file doesn't exist. Must be on durable storage — if this path doesn't survive a restart (e.g. it's inside an ephemeral container filesystem), Fence generates a new key every time it restarts, invalidating every previously-issued token and forcing all users to log in again.~/.fence/signing-key.jwk
FENCE_REDIRECT_URIWhere Fence sends the browser back to after sign-in — your Canopy UI's callback URL.http://localhost:3000/callback
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRETOAuth client credentials for the upstream identity provider (Google by default) that Fence delegates the actual sign-in step to. Required unless running in standalone mode.—

By default Fence delegates the human sign-in step to an upstream OIDC provider (Google, via the credentials above) rather than storing passwords itself. If you'd rather run Fence without wiring up an upstream provider or a full OAuth2 client registry — for local testing or a single-operator deployment — set fence.standalone.enabled: true, which trades that off for a simpler, permissive login flow. This is not recommended for a production deployment exposed to a network.

SSH Gateway

Direct SSH to a seedling (trowel grove connect, sshConnectionString) is only appropriate for local development. A production deployment always runs the SSH gateway (the gateway module), so no seedling's sshd is exposed to users. Cultivators connect to a single gateway endpoint, which authenticates them and relays the session to their grove's seedling:

code
ssh -i ~/.orchard/keys/default -p 2222 <grove-id>@gateway.your-domain.com

The gateway accepts either a key registered with trowel ssh-key add or a short-lived gateway token entered as the password. Orchard doesn't block direct seedling SSH on its own. The gateway is required because it's the only production setup Orchard supports. It's a separate process, published as a JAR on each release, and needs Fence and the core API running:

code
curl -LO https://github.com/orchard-cde/orchard/releases/latest/download/gateway.jar
java -jar gateway.jar
Env varPurposeDefault
GATEWAY_SSH_PORTPort cultivators connect to.2222
GATEWAY_ADMIN_PORTAdmin/actuator port (/actuator/health). Keep it internal.8081
GATEWAY_HOST_KEY_PATHThe gateway's SSH host key. Generated on first boot if missing. Must be on durable storage, or the host key changes on every restart and clients get host-key-mismatch warnings.~/.orchard/gateway-host-key
GATEWAY_INTERNAL_SSH_KEY_PATHPrivate key the gateway uses to log in to seedlings. The gateway won't start if it can't read this file.~/.ssh/orchard_ed25519
GATEWAY_FENCE_ISSUER_URIYour Fence issuer URL (same value as FENCE_ISSUER).http://localhost:7779
GATEWAY_OAUTH2_CLIENT_ID / GATEWAY_OAUTH2_CLIENT_SECRETCredentials the gateway uses to get its service token from Fence.orchard-gateway / empty
GATEWAY_TRELLIS_BASE_URLCore API URL.http://localhost:8080

Client secret. Fence registers the gateway as a confidential client from FENCE_GATEWAY_CLIENT_ID / FENCE_GATEWAY_CLIENT_SECRET. These must match the gateway's GATEWAY_OAUTH2_CLIENT_ID / GATEWAY_OAUTH2_CLIENT_SECRET, or the gateway can't authenticate to the core API. Both secrets default to empty, so set a strong shared secret on both sides.

Internal key. The gateway logs in to seedlings with its internal key, so that key must pair with the public key Orchard installs on seedlings. The simplest setup points GATEWAY_INTERNAL_SSH_KEY_PATH at the same private key as your provider's ssh-key-path:

  • QEMU: orchard.qemu.ssh-key-path. Seedlings get orchard.qemu.ssh-public-key (or ORCHARD_SSH_PUBLIC_KEY) if it's set, otherwise <ssh-key-path>.pub.
  • AWS EC2: orchard.nursery.aws.ssh-key-path. Seedlings get <ssh-key-path>.pub.

All three settings default to ~/.ssh/orchard_ed25519. Keep the private key on durable storage and readable only by the Orchard and gateway service accounts.

Network. The gateway connects to each seedling at the IP address and SSH port Orchard records for it:

  • QEMU: seedlings are recorded as 127.0.0.1:<forwarded port>, so run the gateway on the same host as the core API. QEMU's port forwards listen on all interfaces, so firewall the ssh-port-range (default 49152–49999) from outside the host.
  • AWS EC2: the gateway host must reach instances on port 22. Restrict the instance security group to the Orchard server and gateway hosts.

Expose only the gateway's SSH port to cultivators.

Production Configuration

Edit application.yml (or override via environment variables) before starting:

code
orchard:
  security:
    oauth2:
      enabled: true        # Always enable in production
 
  qemu:
    ssh-port-range-start: 49152
    ssh-port-range-end: 49999
    enable-kvm: true
    ssh-public-key: <your-public-key>
 
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://fence.your-domain.com   # your Fence instance

If you already run an external OAuth2/OIDC identity provider (Keycloak, Auth0, Okta, etc.) and prefer to use it instead of Fence, issuer-uri accepts any standards-compliant issuer — the resource server config isn't tied to Fence specifically. See API Authentication for how Trowel authenticates against Fence.

Health Checks

Monitor your deployment with the health endpoint:

code
curl https://orchard.example.com/api/health

Expected response:

code
{
  "status": "healthy",
  "name": "Orchard",
  "version": "0.4.0"
}

Check provider readiness (e.g. QEMU availability):

code
curl https://orchard.example.com/api/health/ready

Returns 200 when the configured seedling provider is available, 503 otherwise.

Updates

Stop the running processes, update each one, then start them again. The bootRun and java -jar commands run in the foreground until stopped, so run each process in its own terminal or under a process manager such as systemd. Start the core API and Fence before the gateway, which needs both.

Core API:

code
git pull
./gradlew :trellis:bootRun

Fence:

code
curl -LO https://github.com/orchard-cde/orchard/releases/latest/download/fence.jar
java -jar fence.jar --server.port=7779

SSH gateway:

code
curl -LO https://github.com/orchard-cde/orchard/releases/latest/download/gateway.jar
java -jar gateway.jar