Docs / Install

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: Retain and allowVolumeExpansion: true. On AWS the installer ships an encrypted gp3 class named skela-gp3.
  • cert-manager and an Issuer in the skela namespace, or a ClusterIssuer, that can sign cluster-internal names such as skela-control.skela.svc. Use a CA, Vault or private PKI issuer: an ACME issuer cannot sign internal names. The issuer must write ca.crt into 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-Match and If-Match). The platform checks conditional writes at startup and refuses a store that ignores them. bucket.sh in 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 (or shasum) 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.sh options: SKELA_BUCKET_KMS_KEY (customer-managed key), SKELA_BUCKET_VERSIONING=1, SKELA_BUCKET_OBJECT_LOCK_DAYS=<n> and SKELA_BUCKET_REPLICA with SKELA_BUCKET_REPLICATION_ROLE for 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.