Skip to content

Deploy to your cloud

There are two separate deploys. pal deploy ships an agent to a running control plane, in seconds, as often as you change the agent. Helm or Terraform puts the platform in your cloud, once. For a gated rollout through sandbox, staging and prod, with a human approving prod, use Promotion instead of a bare pal deploy.

What you get at the end: the dashboard on your own app host, the API on your own API host, a first owner account, and the CLI signed in to it.

From the agent’s directory, against any control plane you are signed in to:

Terminal window
pal deploy --dry-run # one read-only lookup: reports "create" or "update", changes nothing
pal deploy --json

pal deploy reads agent.yaml (name, description, model, system_prompt, labels, workflow), creates or updates the agent, saves the workflow graph, then calls POST /v1/agents/{name}/deploy. The response carries "status": "live" and the agent’s invoke URL.

Exit code Meaning
3 Unauthorized
4 Quota or budget (402)
5 Not found
6 Server error
Two ingress hosts. The app host routes to the dashboard. The API host routes /v1, /auth, /.well-known, /mcp and /dl to the control plane on :8080. The gateway and the internal services are not on the ingress and use mutual TLS.Two ingress hosts. The app host routes to the dashboard. The API host routes /v1, /auth, /.well-known, /mcp and /dl to the control plane on :8080. The gateway and the internal services are not on the ingress and use mutual TLS.

You need: a Kubernetes cluster whose CNI enforces NetworkPolicy (Calico, Cilium), cert-manager (the chart issues every internal mTLS certificate with it), an ingress controller, managed Postgres with pgvector, and an S3 bucket.

  1. Generate the secrets into a file you keep out of git. Each one stops a pod, with its name in the log, if it is missing in production. You pass this same file again on every upgrade.

    Terminal window
    umask 077
    cat > pal-secrets.yaml <<EOF
    secrets:
    jwtSecret: "$(openssl rand -hex 32)"
    credentialKey: "$(openssl rand -hex 32)"
    receiptSecret: "$(openssl rand -hex 32)"
    callbackSigningSecret: "$(openssl rand -hex 32)"
    grpcServiceToken: "$(openssl rand -hex 32)"
    schedulerNodeToken: "$(openssl rand -hex 32)"
    appDbPassword: "$(openssl rand -hex 32)"
    postgresPassword: "<your managed Postgres password>"
    minioPassword: "<your S3 secret access key>"
    EOF

    callbackSigningSecret signs run and batch callbacks. appDbPassword is for the restricted pal_app role that row-level security applies to: create the role with infra/db/least-privilege.sql, then set the same password on it (ALTER ROLE pal_app PASSWORD '…').

  2. Write your values. Save this as my-values.yaml and replace the hosts, store addresses and the ingress controller’s pod CIDR and namespace.

    my-values.yaml
    externalDatabase:
    host: pal-db.example.internal # Postgres with pgvector
    externalS3:
    endpoint: https://s3.us-east-1.amazonaws.com
    region: us-east-1
    accessKeyId: <access key id> # the secret key is in pal-secrets.yaml
    bucket: <your bucket>
    ingress:
    enabled: true
    hosts:
    - host: api.example.com
    paths:
    - {path: /v1, pathType: Prefix, service: control-plane}
    - {path: /auth, pathType: Prefix, service: control-plane}
    - {path: /.well-known, pathType: Prefix, service: control-plane}
    - {path: /mcp, pathType: Prefix, service: control-plane}
    - {path: /dl, pathType: Prefix, service: control-plane}
    - host: app.example.com
    paths:
    - {path: /, pathType: Prefix, service: dashboard}
    tls:
    - hosts: [api.example.com, app.example.com]
    secretName: pal-tls
    controlPlane:
    trustedProxies: "10.0.0.0/8" # your ingress controller's pod CIDR
    env:
    PAL_BOOTSTRAP_OWNER_EMAIL: you@example.com
    networkPolicy:
    ingressFrom:
    - namespaceSelector:
    matchLabels:
    kubernetes.io/metadata.name: ingress-nginx

    Why each line is there:

    • Two hosts. The dashboard’s pages and the API share paths such as /auth/reset, so they cannot share one origin. Do not point the API host at the gateway: it serves only /v1/agents and /v1/runs. /internal and /metrics are never routed.
    • trustedProxies and ingressFrom are required. The chart refuses to render without them when ingress is on: the first is how the sign-in rate limiter finds the real client IP, the second keeps other namespaces off the control plane’s port.
    • tls. The chart derives PAL_PUBLIC_BASE_URL, OAUTH_REDIRECT_BASE, OAUTH_DASHBOARD_URL, PAL_DASHBOARD_URL and the dashboard’s API URL from these hosts, and uses https:// only for hosts listed under tls.
    • PAL_BOOTSTRAP_OWNER_EMAIL. Sign-up is closed in production and there are no users yet; this creates the first owner.
  3. Install.

    Terminal window
    helm dependency build ./infra/helm/pal # the Redis subchart
    helm install pal ./infra/helm/pal --namespace pal --create-namespace \
    -f infra/helm/pal/values-prod.yaml -f my-values.yaml -f pal-secrets.yaml

    values-prod.yaml sets PAL_ENV=production, so every production startup guard is on. Check it worked: kubectl -n pal get pods shows every pod Running and curl -s https://api.example.com/auth/providers returns JSON.

  4. Sign in as the first owner. The control plane prints a one-time password link, valid for one hour, once:

    Terminal window
    kubectl -n pal logs deploy/pal-control-plane | grep -A2 "first-owner bootstrap"

    Open the link, set your password, then sign in at https://app.example.com. For Google sign-in, register https://api.example.com/auth/oauth/google/callback. More in Accounts and teams.

  5. Point the CLI at it. Without --rest, --api-url is a gRPC address. Set PAL_API_URL and PAL_USE_REST=1 to make it permanent.

    Terminal window
    pal --rest --api-url https://api.example.com login

This all-in-one chart suits a single cluster. To keep agent data in a separate VPC, install infra/helm/pal-control-plane here and a data plane per cloud (next tab). Every value, the store choices and backups: Production deployment.

Topology Control plane Data plane When
Hybrid (recommended) One place Each workload’s cloud Production; agent data stays in your VPC
Fully self-hosted Your cluster Your cluster Full control; you run Postgres, Redis and Kubernetes
Fully managed Not offered Not offered Not available yet

Next: Production deployment for production settings, NetworkPolicy, backups and migrations. Every variable a production process requires: Environment variables. Shape of the split: Deployment topology.