Install SkelaDB
SkelaDB runs in your own Kubernetes cluster and cloud account. You download a signed release bundle, point the installer at your cluster, bucket and certificate issuer, and it installs the platform from images pinned by digest. Nothing is built on your machines and nothing leaves your account.
This guide installs the database platform: the controller, the connection proxy and, by default, object-storage-backed projects. The optional web console is described in console.md.
What you need
- A Kubernetes cluster, version 1.29 or later (1.33 is the version we test), with a CNI that enforces NetworkPolicy. Object storage mode places three WAL nodes per project on different nodes, so plan for at least three worker nodes.
- A CSI StorageClass for disks. Prefer
reclaimPolicy: RetainandallowVolumeExpansion: true. On AWS the installer ships an encrypted gp3 class namedskela-gp3. - cert-manager and an
Issuerin theskelanamespace, or aClusterIssuer, that can sign cluster-internal names such asskela-control.skela.svc. Use a CA, Vault or private PKI issuer: an ACME issuer cannot sign internal names. The issuer must writeca.crtinto the Secrets it issues for object storage mode; for dedicated disks only, a separate CA bundle Secret is accepted (below). - An object storage bucket: Amazon S3, Google Cloud Storage, Azure Blob Storage,
or an S3-compatible store that honours conditional writes (
If-None-MatchandIf-Match). The platform checks conditional writes at startup and refuses a store that ignores them.bucket.shin the bundle creates the bucket with public access blocked, encryption at rest and TLS-only access. - Workload identity for the node pools that run databases (IRSA on EKS, Workload Identity Federation on GKE, Entra Workload ID on AKS), or static keys scoped per project for other S3-compatible stores.
- A SkelaDB license key. It is free for organizations under USD 1M annual revenue. See licensing.md. Without a key the platform installs but refuses to create databases.
- On the machine you install from:
bash,kubectl,jq,openssl,curl,sha256sum(orshasum) and cosign. No Docker, Terraform or cloud CLI is needed for the generic install.
1. Download and verify the release
Releases are published at https://skeladb.com/releases/<version>/. Verify
every download before you use it; verify.md explains each step.
V=0.0.1; R=https://skeladb.com/releases/$V
curl -fsSLO https://skeladb.com/releases/cosign.pub
sha256sum cosign.pub # must equal the fingerprint published in verify.md
for f in skeladb-$V.tar.gz SHA256SUMS SHA256SUMS.cosign.bundle; do curl -fsSLO "$R/$f"; done
cosign verify-blob --key cosign.pub --bundle SHA256SUMS.cosign.bundle SHA256SUMS
sha256sum -c SHA256SUMS --ignore-missing
tar xzf skeladb-$V.tar.gz && cd skeladb-$V
The bundle contains install.sh, bucket.sh, the platform manifests and
provider profiles (aws/, gcp/, azure/, kubernetes/), the TLS, console
and monitoring components, release.env, digests.txt, manifest.json, the
license agreement and third-party notices.
release.env names every image by registry and SHA-256 digest. The installer
refuses image tags. To install from a private registry, copy each image by digest
(for example with crane copy or regctl image copy) and set the matching
variable, such as SKELA_PROXY_IMAGE=registry.internal/skeladb/proxy@sha256:...;
a variable already set in your environment overrides release.env. If the
registry needs credentials, create a pull Secret in the skela namespace and set
SKELA_IMAGE_PULL_SECRET to its name.
2. Choose a storage mode
| Mode | SKELA_STORAGE_MODE |
What it does |
|---|---|---|
| Object storage (default) | separated |
Data lives in your bucket. Each project gets a storage pool (a page server and three WAL nodes); branches share unchanged data, so a new branch is created in seconds whatever the database size. Point-in-time restore covers the last 7 days by default. |
| Dedicated disk | pvc |
Each branch keeps its own PostgreSQL disk, like a standard server. A new branch copies its parent. Backups and WAL are archived to your bucket for point-in-time restore. |
The mode is chosen per project when it is created and never changes; the
installation default only decides which modes are available. With
SKELA_STORAGE_MODE=pvc only dedicated disks are installed.
3. Configure
set -a; . ./release.env; set +a
export SKELA_LICENSE_KEY='<your license key>'
export SKELA_INSTALL_PROVIDER=aws # aws, gcp, azure or kubernetes
export SKELA_STORAGE_CLASS=skela-gp3 # an installed CSI StorageClass
export SKELA_BACKUP_BUCKET=my-skeladb-data
export SKELA_NETWORK_CLOUD_CIDRS='<object storage and identity endpoint CIDRs>'
export SKELA_TLS_ISSUER=skela-ca
export SKELA_TLS_ISSUER_KIND=ClusterIssuer # default: Issuer in namespace skela
export SKELA_TLS_DNS_NAME=sql.example.com # the host name SQL clients connect to
| Variable | Required | Meaning |
|---|---|---|
SKELA_LICENSE_KEY |
yes, on first install | Your license key. The installer stores it in the skela-license Secret. |
SKELA_INSTALL_PROVIDER |
no, default kubernetes |
Provider profile: load balancer annotations, identity and backup scheme. |
SKELA_STORAGE_MODE |
no, default separated |
separated (object storage) or pvc (dedicated disks only). |
SKELA_STORAGE_CLASS |
yes | StorageClass for database, WAL and seed disks. |
SKELA_BACKUP_BUCKET |
yes, unless per-project buckets | Bucket (or Azure container) for backups and object storage. |
SKELA_BACKUP_PER_PROJECT |
no | 1 requires every project to name its own bucket; leave SKELA_BACKUP_BUCKET unset. |
SKELA_BACKUP_AZURE_ACCOUNT |
Azure only | Storage account name. |
SKELA_NETWORK_CLOUD_CIDRS |
yes | Comma-separated CIDRs that database and storage pods may reach over HTTPS: your object storage and identity endpoints (for example S3 and STS VPC endpoint addresses). 0.0.0.0/0 is refused. |
SKELA_NETWORK_CLOUD_PORTS |
no, default 443 |
Up to eight ports for those CIDRs, for example 443,9000 for a private MinIO. |
SKELA_TLS_ISSUER, SKELA_TLS_ISSUER_KIND |
yes, kind defaults to Issuer |
cert-manager issuer for every platform and database certificate. |
SKELA_TLS_DNS_NAME |
yes | SQL host name, added to the proxy certificate. |
SKELA_TLS_CA_SECRET |
no | Secret in skela whose ca.crt is the issuing CA, for issuers that do not write ca.crt (dedicated-disk installs only). |
SKELA_IMAGE_PULL_SECRET |
no | Pull Secret for every platform, database and storage pod. |
SKELA_EBS_KMS_KEY |
no, AWS | KMS key ARN for the bundled skela-gp3 class. |
SKELA_CREATE_BUCKET |
no | 1 runs bucket.sh for the shared bucket during apply (aws, gcp, azure). |
SKELA_KUBE_CONTEXT |
yes, for apply |
The exact kubeconfig context to install into. The installer never uses the current context implicitly. |
Every input is checked before anything is rendered, with a message naming the variable and what to set.
4. Render, review and apply
./install.sh render-retained > retained.yaml # the skela namespace and the Branch resource definition
./install.sh render > install.yaml # the platform: controller, proxy, policies, certificates
export SKELA_KUBE_CONTEXT='<your context>'
./install.sh apply
Rendering works offline and never includes secret values. Review both files before applying. The namespace and the Branch resource definition are kept separate on purpose: deleting either deletes every database, so they are never part of the bundle you remove on uninstall.
apply renders again, checks that cert-manager and the issuer exist, checks the
StorageClass (or the EBS CSI driver for skela-gp3) and any pull or CA Secret,
applies the namespace and resource definition, stores the license key, creates
the skela-api (API, proxy and metrics tokens) and skela-proxy-peer Secrets
once without printing them, applies the platform and waits for the controller and
proxy to become ready. It never provisions cloud infrastructure and never changes
your current kubeconfig context. The namespace is always skela.
AWS in one step
aws/install.sh creates or reuses a KMS key for Kubernetes Secrets, an EKS
cluster on Graviton nodes with the EBS CSI driver, the bucket, then runs
./install.sh apply and creates each project’s identity:
set -a; . ./release.env; set +a
export SKELA_LICENSE_KEY='<your license key>'
PROJECTS="acme analytics" REGION=us-east-1 \
SKELA_NETWORK_CLOUD_CIDRS='<S3 and STS endpoint CIDRs>' \
SKELA_TLS_ISSUER=skela-ca SKELA_TLS_ISSUER_KIND=ClusterIssuer SKELA_TLS_DNS_NAME=sql.example.com \
aws/install.sh
It is safe to rerun. It writes the API token to ~/.skela/<cluster>-api-token
(mode 0600) and prints the internal load balancer host name of the proxy.
5. Create project identities
Every project has its own storage identity: a ServiceAccount
skela-branch-<project> and a Secret skela-backup-<project>, both labelled
skeladb.io/project=<project>, and a cloud grant limited to
<bucket>/skela/<project>/. The platform refuses to provision a project without
them. One script per provider creates all three and is safe to rerun:
| Provider | Command |
|---|---|
| AWS | SKELA_KUBE_CONTEXT=... BUCKET=... CLUSTER=<eks> REGION=<region> aws/project.sh <project>... |
| Google Cloud | SKELA_KUBE_CONTEXT=... BUCKET=... SKELA_GCP_PROJECT=<project id> gcp/project.sh <project>... (the bucket needs uniform bucket-level access) |
| Azure | SKELA_KUBE_CONTEXT=... BUCKET=<container> SKELA_AZURE_RESOURCE_GROUP=<group> SKELA_AKS_CLUSTER=<aks> SKELA_BACKUP_AZURE_ACCOUNT=<account> azure/project.sh <project>... |
| Other S3-compatible | SKELA_KUBE_CONTEXT=... BUCKET=... AWS_REGION=... AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... [AWS_ENDPOINT=...] kubernetes/project.sh <project> with keys you limited to that project’s prefix, one project per run |
The scripts never delete cloud resources. Before production, check from a database pod that a project can read and write its own prefix and is refused on another project’s prefix.
6. Check the installation
kubectl --context "$SKELA_KUBE_CONTEXT" -n skela get deploy,pods
kubectl --context "$SKELA_KUBE_CONTEXT" -n skela logs deploy/skela-control | grep -i license
Then create a first database: first-project.md.
Network and encryption checklist
- Keep object storage traffic private: an S3 gateway VPC endpoint (plus
interface endpoints for STS and ECR), Private Google Access or Private Service
Connect, or an Azure private endpoint for Blob storage. List those addresses
in
SKELA_NETWORK_CLOUD_CIDRS. - The proxy is exposed through an internal load balancer on aws, gcp and azure and a ClusterIP Service on generic Kubernetes. Expose SQL to other networks through a private endpoint service rather than a public load balancer.
- Encrypt Kubernetes Secrets at rest (EKS KMS envelope encryption, GKE application-layer Secrets encryption, AKS KMS etcd encryption).
bucket.shoptions:SKELA_BUCKET_KMS_KEY(customer-managed key),SKELA_BUCKET_VERSIONING=1,SKELA_BUCKET_OBJECT_LOCK_DAYS=<n>andSKELA_BUCKET_REPLICAwithSKELA_BUCKET_REPLICATION_ROLEfor a second region. Object lock prevents anyone, including a compromised database pod, from deleting backups during the retention period; we recommend it for production.- Every platform connection uses TLS: the proxy, the controller API, database pods and the storage services refuse plaintext on routable addresses.
Monitoring
observability/ holds Prometheus Operator alert rules and scrape targets, and on
AWS a Fluent Bit DaemonSet that ships logs to CloudWatch Logs. On GKE and AKS use
the managed log agents instead. The controller’s /metrics endpoint needs the
metrics-token key of the skela-api Secret as a bearer token; the bundled
PodMonitor already uses it. All services log JSON lines.