Administration

Install a service from an image

Deploy a published service from an OCI image into a running Trellis environment.

This guide covers installing and running a service from a published OCI image. You do not need the service source tree — the contract artifact is embedded in the image.

If you are developing a service locally and have the source checked out, see Install a service from source instead.

What you need

  • a running Trellis environment
  • the trellis CLI installed (see Install the Trellis CLI)
  • admin access to the Trellis instance
  • a published OCI image for the service (e.g., ghcr.io/my-org/my-service:1.2.3)

1. Log in as an admin

Create or refresh the local administrator session:

trellis --nats-servers localhost auth login \
  --auth-url http://localhost:3000 \
  --provider github

Confirm the session is active:

trellis auth status

2. Install the contract from the image

trellis service install --image ghcr.io/my-org/my-service:1.2.3

The CLI pulls the image, reads the contract artifact from it (at the path declared by the io.trellis.contract.path label, or /trellis/contract.json by default), generates a session key, and installs the contract.

The CLI will:

  • generate a new Ed25519 session seed locally
  • derive the public session key for the service
  • infer defaults such as display name, description, and namespaces from the contract
  • show an installation review before sending the request

3. Store the seed immediately

After a successful install, the CLI prints:

installed service contract
sessionKey=<public-key>
contractId=trellis.my-service@v1
contractDigest=<digest>
seed=<private-seed>
store the seed securely; it will not be shown again

Save the seed to your secret manager or deployment system immediately. The CLI will not print it again.

4. Run the container

Run the same image you installed from. Pass the session seed and NATS connection details as environment variables:

podman run --rm \
  -e MY_SERVICE_SESSION_KEY_SEED="<seed from install>" \
  -e NATS_SERVERS=host.containers.internal \
  -e NATS_SENTINEL_CREDS=/run/creds/sentinel.creds \
  -v /path/to/sentinel.creds:/run/creds/sentinel.creds:ro \
  --network trellis.network \
  ghcr.io/my-org/my-service:1.2.3

For a permanent deployment, create a Quadlet unit for the service similar to the runtime and console units in the Starting Trellis guide.

5. Upgrade to a new version

trellis service upgrade --image ghcr.io/my-org/my-service:1.2.4

If multiple installed services use the same contract id, target the correct one:

trellis service upgrade --image ghcr.io/my-org/my-service:1.2.4 --service-key <public-key>

Upgrade keeps the service key the same and updates the installed contract digest. Restart the container with the new image tag after upgrading.

What install actually grants

Installation is not only metadata storage. It is the control-plane step that determines what the service is allowed to do.

Trellis derives service access from:

  • the service-owned RPC, event, and subject surfaces declared in the contract
  • explicit dependencies declared under uses
  • runtime-managed resource bindings created during install or upgrade

Useful flags

FlagCommandWhat it does
--display-nameservice installOverrides the default display name inferred from the contract
--descriptionservice installStores an explicit operator-facing description
--namespaceservice installAdds extra namespaces beyond the ones inferred from the contract
--inactiveservice installInstalls the service in an inactive state
-fbothSkips the interactive confirmation prompt
--service-keyservice upgradeTargets a specific installed service

Practical deploy loop

  1. trellis auth login
  2. trellis service install --image ghcr.io/my-org/my-service:1.2.3
  3. store the seed
  4. start the container with the right env
  5. confirm the expected RPCs, events, and bindings work

Install or upgrade should always happen before the container starts serving traffic.

If you are building service images, include the generated contract artifact at /trellis/contract.json. If the image uses a different path, add an OCI label such as io.trellis.contract.path=/path/to/contract.json so the CLI can discover it automatically.