Set up the console
The console is an optional web application for your teams: organizations,
projects, branches, databases, a SQL editor, restore, monitoring, alerts and API
keys for the CLI and agents. It runs in its own namespace, skela-cloud, as
three workloads (the console, its API and a background worker) and keeps its
state in a PostgreSQL database you operate, the management database. Sign-in uses
your own WorkOS environment; SkelaDB never sees your users.
Install the platform first (install.md). The console then talks to the platform’s controller in each region you configure.
A read/write project API key can execute SQL and reveal credentials for every database in that project, including its main branch. A branch’s writes do not reach its parent, but a project key is not limited to that branch. Keep production in a separate project from agent automation, or give an agent only its disposable branch’s database credential.
1. WorkOS
Create a WorkOS environment for this installation.
- Under Redirects, add the callback
https://console.example.com/api/auth/callbackand the logout return URIhttps://console.example.com/. - Create organization roles with the slugs
adminandmember(owneris also accepted as an administrator role). Organization administrators manage every project; members need areaderordevelopergrant per project, set in the console under Team & access. - Under Webhooks, add
https://api.example.com/webhooks/workosand subscribe touser.*,organization.*,organization_membership.*andsession.*. Each event makes the API drop cached identity reads, so a removed member loses access on the next request. Events never grant access. - Turn on “MFA required” before you name any platform operator (below); the API requires MFA for operator sessions.
- For sign-in and invitation mail, configure WorkOS’s custom email provider (Amazon SES or your own) so messages come from your domain.
Note the client ID, the API key, the webhook signing secret and the exact issuer of the environment.
2. Management database
Use a managed PostgreSQL (RDS, Cloud SQL, Azure Database for PostgreSQL) or a
PostgreSQL you operate (we test with PostgreSQL 18), reachable from the
skela-cloud namespace.
Create a database and an owner role for the console, and turn on point-in-time
recovery. The connection URL must use sslmode=verify-full; for a private CA,
mount the CA file and add sslrootcert=<path> to the URL.
Plan connections: each API replica uses SKELA_CLOUD_DB_POOL_SIZE (default 16),
and each worker that plus SKELA_CLOUD_WORKER_CONCURRENCY (default and minimum 2).
One worker slot is reserved for URL imports; the remaining slots handle lifecycle
operations even while an import is copying data.
3. Encryption keys
The management database stores database passwords, import sources, saved scripts, alert destinations and query history sealed with keys that only your installation holds. Generate one and escrow it before you start:
echo "1:$(openssl rand -base64 32)"
SKELA_CLOUD_ENCRYPTION_KEYS is a comma-separated list of id:base64key, newest
first, where id is a number from 1 to 255. Losing every key that sealed a value loses that value; restoring a
database backup without its keys leaves its credentials unreadable. See
backup-and-restore.md
and, to rotate, rotate-keys-and-certificates.md.
4. Regions and project slots
The console places each new project in a region: a platform installation and a prepared project identity (a slot). Prepare slots on the platform cluster:
-
Create the project identity with the provider script from install.md, for example
aws/project.sh slot-001. -
Give the slot its own API token in the
skela-project-tokensSecret, which maps a project name to a token. A project token reaches only its own project:kubectl -n skela create secret generic skela-project-tokens \ --from-literal=slot-001="$(openssl rand -hex 32)" --dry-run=client -o yaml | kubectl apply -f -To add slots later, add keys to the same Secret. The controller reads it on every request, so a replaced or removed token stops working at once. Slot tokens must be at least 32 characters.
-
Describe the region in a JSON file, which you store in the
cloud-regionsSecret (mounted at/etc/skela):[{ "id": "aws-us-east-1", "name": "US East (Virginia)", "provider": "aws", "postgres_majors": [18], "separated_postgres_majors": [18], "controller_url": "https://skela-control.skela.svc:8080", "controller_ca": "/etc/skela/control-ca.pem", "compute_ca": "/etc/skela/compute-ca.pem", "proxy_host": "sql.example.com", "proxy_port": 6432, "slots": [{"id": "slot-001", "token": "<the slot's token>"}] }]controller_caandcompute_caare the CA certificates of your cert-manager issuer, stored as keys of the same Secret; each key becomes a file directly under/etc/skela:kubectl -n skela-cloud create secret generic cloud-regions \ --from-file=regions.json --from-file=control-ca.pem=ca.crt --from-file=compute-ca.pem=ca.crtproxy_hostandproxy_portare what your users connect to: theSKELA_TLS_DNS_NAMEyou installed with and the proxy’s load balancer port. Never put the controller’s admin token in a slot. Release 0.0.1 offers PostgreSQL 18 only. A region in object storage mode lists 18 inseparated_postgres_majors, and new projects default to object storage there.
Each project uses one slot. Deleted projects keep their slot until their retained
backups are purged after SKELA_CLOUD_DELETION_RETENTION_DAYS (default 7).
5. Settings
The API and worker read the cloud-config Secret; the console reads
console-config. Create both in skela-cloud (the namespace is created by the
console manifests; create it first with kubectl create namespace skela-cloud
if you create the Secrets before installing).
cloud-config (API and worker):
| Variable | Meaning |
|---|---|
SKELA_CLOUD_DATABASE_URL |
Management database URL with sslmode=verify-full. |
SKELA_CLOUD_ENCRYPTION_KEYS |
Sealing keys, id:base64key with numeric IDs 1 to 255, newest first. |
SKELA_CLOUD_REGIONS_FILE |
Path of the regions file, for example /etc/skela/regions.json. |
SKELA_WORKOS_CLIENT_ID, SKELA_WORKOS_API_KEY |
From your WorkOS environment. |
SKELA_WORKOS_ISSUER |
The exact issuer of the environment. Tokens from any other issuer or client are refused. |
SKELA_WORKOS_WEBHOOK_SECRET |
Webhook signing secret. |
SKELA_CLOUD_OPERATOR_IDS |
Comma-separated WorkOS user IDs (user_...) of platform operators. Anything else stops startup. |
SKELA_CLOUD_ORG_CREATORS |
Who besides operators may create organizations: user IDs, email addresses or @domain entries, comma-separated. Unset: operators only. Everyone else joins by invitation. |
SKELA_CLOUD_MAX_PROJECTS_PER_ORG |
Active projects per organization, default 10; 0 removes the limit. |
SKELA_CLOUD_METRICS_TOKEN |
Bearer token, at least 32 characters, for the API’s /metrics. |
SKELA_CLOUD_MAIL_FROM and mail settings |
Product mail (alerts, notifications); see below. |
SKELA_CLOUD_AUDIT_RETENTION_DAYS |
Days the audit trail is kept, default 365 (365 to 3650). |
SKELA_CLOUD_DELETION_RETENTION_DAYS |
Days a deleted project’s backups are kept before purge, default 7 (1 to 90). |
SKELA_CLOUD_IMPORT_ALLOW_PRIVATE |
CIDRs of private networks that database URL imports may read from. Public addresses are always allowed. |
console-config (console):
| Variable | Meaning |
|---|---|
SKELA_CONSOLE_ORIGIN |
https://console.example.com. |
SKELA_WORKOS_REDIRECT_URI |
https://console.example.com/api/auth/callback. |
SKELA_WORKOS_CLIENT_ID, SKELA_WORKOS_API_KEY |
Same as the API. |
SKELA_WORKOS_COOKIE_PASSWORD |
At least 32 random characters (openssl rand -base64 32). Changing it signs everyone out. |
SKELA_CLOUD_API_URL |
http://cloud-api.skela-cloud.svc.cluster.local:8090 with SKELA_CLOUD_API_INTERNAL=1. Any other API URL must be HTTPS. |
SKELA_PUBLIC_API_URL |
The HTTPS address your users and agents reach the API at, for example https://api.example.com. The console shows it in the CLI & agents snippets; unset, they show a placeholder. |
SKELA_CONSOLE_TRUSTED_PROXIES |
Pod CIDRs of your ingress controller, so the audit trail records the real client address. |
SKELA_CONSOLE_IDLE_MINUTES |
Sign out after this many idle minutes, default 60 (5 to 1440). |
SKELA_CONSOLE_SESSION_HOURS |
Longest session, default 168 (1 to 720). Operator sessions always end after 30 minutes. |
The console checks every setting when it starts and exits with a list of the problems instead of failing later. It refuses the example cookie password.
6. Mail
Product mail is sent by the worker.
- Amazon SES: set
SKELA_CLOUD_MAIL_FROM,SKELA_CLOUD_MAIL_CONFIGURATION_SETandAWS_REGION, and give the worker’s identityses:SendEmailfor that sender. For bounce and complaint handling, route the configuration set’s events through SNS to a dedicated SQS queue and setSKELA_CLOUD_FEEDBACK_QUEUEto its URL; bounced and complaining addresses are then suppressed. - SMTP (Google Cloud, Azure or any relay): set
SKELA_CLOUD_SMTP_URLandSKELA_CLOUD_MAIL_FROM.smtps://user:password@smtp.example.com:465uses implicit TLS,smtp://user:password@smtp.example.com:587requires STARTTLS. Percent-encode reserved characters in the credentials. Handle bounces at the relay.
Allow the worker’s egress to the mail service.
7. Ingress and TLS
Expose the console and the API through your TLS-terminating ingress controller.
The API needs only /v1 and /webhooks/workos on the public side. Set
SKELA_INGRESS_NAMESPACE to your ingress controller’s namespace (default
ingress-nginx); the console’s NetworkPolicies admit traffic only from there.
To have the installer render the Ingress objects too, set
SKELA_INGRESS_CLASS, SKELA_CONSOLE_HOST, SKELA_API_HOST and
SKELA_INGRESS_TLS_SECRET (a TLS Secret in skela-cloud for both hosts, for
example issued by cert-manager with ACME).
Database URL imports from providers on the public internet need
SKELA_CLOUD_PUBLIC_IMPORTS=1, which allows PostgreSQL egress to public addresses.
Alert destinations (Slack, webhooks) and WorkOS need HTTPS egress.
8. Install
Install the platform first (install.md). Then, with the same release loaded and your kubectl context set to the platform cluster (this step uses the current context), run the console release:
set -a; . ./release.env; set +a
kubectl config use-context "$SKELA_KUBE_CONTEXT"
cloud/render.sh apply
apply runs this release’s database migration Job and waits for it. If the
migration fails it prints the Job’s log and stops, leaving any running release
untouched. It then rolls out the API, worker and console and waits for each. The
workloads only check the schema; they never migrate it, and they refuse to start
against a schema older than their release. cloud/render.sh app prints the
workload manifests for review without applying anything.
9. First sign-in and the first operator
- Open the console and sign in.
- Open Profile settings from the account menu and copy your User ID
(
user_...). - Add it to
SKELA_CLOUD_OPERATOR_IDSincloud-configand restart the API:kubectl -n skela-cloud rollout restart deployment/cloud-api. - The Platform entry appears in the account menu. Operators see platform health, regions and capacity.
Create your organization (operators and SKELA_CLOUD_ORG_CREATORS only), invite
your team, then create a project: first-project.md.
Health and logs
The console serves /healthz and /readyz (ready only while the API answers);
the worker serves both on port 8091. Every request gets an X-Request-Id that
appears in both console and API logs. Logs are JSON lines and never contain
tokens, passwords, connection strings or query text. Request bodies over 2 MiB are
refused.