All templates

lucasaarch/cubeship-jian-template

Jian

Jian, the self-hosted agent gateway, with its web panel and a managed PostgreSQL

Jian on Cubeship

Jian is a self-hosted agent gateway that gives AI agents a persistent identity. A profile keeps its own instructions, model, memory and tools, and every session and channel that belongs to it — the web panel, WhatsApp, Telegram, its API — works from that same context.

This template installs the gateway on a Cubeship instance, with its web panel on a domain and everything it keeps in a managed PostgreSQL.

What it creates

  • jian — Jian 1.0.1, from ghcr.io/lucasaarch/jian-gateway. The API answers on the domain you choose, and the panel under /ui/ on the same domain. It holds no state of its own: a redeploy loses nothing.
  • jian-db — PostgreSQL 17 with a database named jian, attached to the gateway. Profiles, sessions, history, memories, the run queue and the encrypted vault are all in it. Jian migrates it on start.

It needs Cubeship 0.6.0 or newer.

What you are asked

InputWhat to give
Where the gateway and its panel answerA domain you control, pointed at your instance.
The token you sign in withNothing — the instance generates it and shows it once. Keep a copy.
The key the vault encrypts credentials withNothing — the instance generates it and shows it once. Keep a copy, away from the database backups.

No model provider key is asked for. Add one in the panel instead: it is encrypted into the vault, and a key the template set would be an empty variable on the app whenever you left it blank.

After installing

  1. Open https://<your domain>/ui/ and sign in with the token.
  2. Under Providers, add a key for Anthropic, Gemini or OpenAI, or sign in with ChatGPT.
  3. Create a profile and talk to it.
  4. To reach it from a chat platform, open Channels on the profile. WhatsApp pairs by scanning a QR code. Telegram takes a BotFather token and shows a webhook URL and secret once; point the bot at them with Telegram's setWebhook. A stranger's first message becomes a contact request you approve.

The token is the whole installation

Jian has one owner and one credential. Whoever has the token can read every conversation, change every profile and run anything the agents can. It is the only thing in front of the panel on a public domain. Treat it like a root password. To rotate it, change JIAN_API_TOKEN on the app and redeploy: that also signs every panel session out.

The vault key

Provider keys, MCP tokens and channel credentials are encrypted with the key in JIAN_MASTER_KEYS and never read back. The key lives on the app, not in the database, so a database backup without it cannot open a single stored credential. Keep the generated value somewhere of its own.

To rotate it, add a second entry and make it the active one — keep the old one until every credential has been sent again from the screen that configures it:

JIAN_ACTIVE_KEY_ID=v2
JIAN_MASTER_KEYS={"v1":"<old key>=","v2":"<44 base64 characters for 32 new bytes>"}

What Jian can reach

Outbound calls go to public HTTPS only. Private addresses — including every other app and database on this instance — are refused unless named, exactly, in JIAN_ALLOW_PRIVATE_ORIGINS: for an Ollama on the same instance, http://cubeship-<project>-<environment>-ollama:11434. Metadata and link-local addresses stay blocked whatever it says.

What does not work here

  • Per-client rate limiting. Jian limits requests by connection address and does not trust X-Forwarded-For, so behind the instance's proxy every client shares one limit.
  • More than one copy. Jian migrates on start and copies would race for it. Keep scale at 1; split API and worker with JIAN_ROLE only once you know why.

Updating Jian

The Jian version is the image tag in template.yaml. Jian migrates its database on start and a migration does not undo itself: back the database up before moving to a newer release of this template. Going back to an older image does not go back on the schema.

Resources

The gateway is limited to 1 CPU and 1 GiB of memory, and the database to the same. Raise limits in template.yaml if you need more.


About Cubeship

This is a template for Cubeship — a PaaS you run on your own server: docker push, and it is live, with HTTPS, a database beside it, and a second machine when one stops being enough.

Browse every template at cubeship.dev/templates.

What this creates

jian

ghcr.io/lucasaarch/jian-gateway:1.0.1

jian-db

Postgres 17

template.yaml
# yaml-language-server: $schema=https://cubeship.dev/schema/template/v1.json
version: 1
minCubeship: "0.6.0"
project: jian

inputs:
  - key: domain
    type: domain
    label: Where the gateway and its panel answer
  - key: apiToken
    type: secret
    label: The token you sign in with
    generate: 48
  - key: masterKey
    type: secret
    label: The key the vault encrypts credentials with
    help: Keep a copy outside the database backups. Without it, a restored backup cannot open a single stored credential.
    generate: 43

databases:
  - key: db
    name: jian-db
    engine: postgres
    version: "17"
    database: jian
    limits: { cpu: 1, memory: 1Gi }

apps:
  - key: gateway
    name: jian
    image: ghcr.io/lucasaarch/jian-gateway
    tag: "1.0.1"
    # The API and the panel, under /ui/, on one port.
    port: 4310
    # The one route that answers without the token.
    health: /health
    domains:
      - host: ${input.domain}
    attach:
      # Writes DATABASE_URL, which is all Jian reads. It migrates on start.
      - database: db
    limits: { cpu: 1, memory: 1Gi }
    env:
      JIAN_API_TOKEN: ${input.apiToken}
      JIAN_ACTIVE_KEY_ID: v1
      # Jian wants 32 bytes in base64: 43 characters and one `=`. Letters and
      # digits are all base64, so a generated 43-character secret is a key.
      JIAN_MASTER_KEYS: '{"v1":"${input.masterKey}="}'