EBS Persistent Storage
orka'i provisions Kubernetes PersistentVolumeClaims (PVCs) for application volumes, managed databases, and the in-cluster registry. By default, PVCs use whatever StorageClass the cluster marks as default — on local dev that is usually local-path (node-local disk, not durable across node loss).
To use AWS EBS (durable block storage), you install the EBS CSI driver on your cluster, create a StorageClass (for example gp3), and tell orka'i which class to use. orka'i never calls the EC2 API directly; the CSI driver provisions and attaches volumes when PVCs are created.
This works on Amazon EKS and self-managed K3s on EC2. You do not need EKS specifically — any Kubernetes cluster whose worker nodes are EC2 instances in AWS can use EBS through the same CSI stack.
How it works
orka'i API → PVC (storageClassName: gp3) → EBS CSI driver → EBS volume → pod mount
- orka'i creates a PVC with
storageClassNameset from the deploy target config. - The AWS EBS CSI driver (
ebs.csi.aws.com) provisions an EBS volume in the same region. - Kubernetes schedules the pod on a node in the volume's availability zone and attaches the disk.
Prerequisites
| Requirement | Notes |
|---|---|
| EC2 worker nodes | EBS volumes attach to EC2 instances in the same AWS region |
| EBS CSI driver | aws-ebs-csi-driver — EKS addon or Helm on K3s |
| IAM permissions | CSI controller needs CreateVolume, AttachVolume, DetachVolume, DeleteVolume, etc. |
| StorageClass | e.g. gp3 with allowVolumeExpansion: true |
| orka'i migration | Deploy target config column (included in recent releases) |
Not applicable: local dev K3s in Docker (make docker-up) — nodes are not EC2-backed, so EBS cannot attach. Use the cluster default (local-path) for development.
Step 1 — Install the AWS EBS CSI driver
Choose the path that matches your cluster.
Amazon EKS
Install the managed addon (replace cluster name and region):
aws eks create-addon \
--cluster-name my-cluster \
--addon-name aws-ebs-csi-driver \
--region us-east-1
Configure IAM Roles for Service Accounts (IRSA) so the CSI controller can manage EBS volumes. AWS documents the exact trust policy and IAM policy for the ebs-csi-controller-sa service account in the EBS CSI driver docs.
K3s on EC2
Install the driver with Helm on a cluster that already has worker nodes on EC2:
helm repo add aws-ebs-csi-driver https://kubernetes-sigs.github.io/aws-ebs-csi-driver
helm repo update
helm upgrade --install aws-ebs-csi-driver aws-ebs-csi-driver/aws-ebs-csi-driver \
--namespace kube-system \
--set controller.serviceAccount.create=true \
--set controller.serviceAccount.name=ebs-csi-controller-sa
Grant IAM permissions via the EC2 instance profile attached to each node (or IRSA if your K3s cluster has OIDC configured). The node role needs the permissions listed in the AWS EBS CSI driver IAM policy.
Verify the controller is running:
kubectl get pods -n kube-system -l app.kubernetes.io/name=aws-ebs-csi-driver
Step 2 — Create a StorageClass
Apply a StorageClass that uses the EBS CSI provisioner. This example creates gp3 with encryption and volume expansion enabled:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gp3
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: ebs.csi.aws.com
parameters:
type: gp3
encrypted: "true"
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
Save as gp3-storageclass.yaml and apply:
kubectl apply -f gp3-storageclass.yaml
WaitForFirstConsumer delays provisioning until a pod is scheduled, which helps place the volume in the correct availability zone.
You can omit the default-class annotation if you prefer to set the class only in orka'i (see Step 3).
List StorageClasses to confirm:
kubectl get storageclass
Step 3 — Configure orka'i
orka'i reads the default StorageClass from the deploy target config in Postgres. After running migrations, set default_storage_class on your default target:
UPDATE deploy_targets
SET config = jsonb_set(
COALESCE(config, '{}'::jsonb),
'{default_storage_class}',
'"gp3"'
)
WHERE is_default = true;
Or replace the whole config object:
UPDATE deploy_targets
SET config = '{"default_storage_class": "gp3"}'::jsonb
WHERE id = '00000000-0000-4000-8000-000000000001';
Restart the orka'i API (and worker, if running) so bootstrap reloads the target config:
# however you run orka'i in your environment, e.g.
systemctl restart orkai-api
When default_storage_class is empty, orka'i omits storageClassName on PVCs and the cluster default applies — same behavior as before this feature.
What gets EBS-backed
Once configured, new PVCs created by orka'i use the configured class:
| Workload | PVC name pattern |
|---|---|
| App volume | {k8s-name}-{volume-name} |
| Managed database | {k8s-name}-data |
| In-cluster registry | registry-data (namespace orkai-system) |
Existing PVCs are not retroactively changed. Only newly created claims pick up the StorageClass.
Step 4 — Verify
Create an app volume from the UI (Project → App → Volumes), save, and redeploy. Then inspect the PVC:
kubectl get pvc -A
kubectl describe pvc <pvc-name> -n <namespace>
You should see:
StorageClass: gp3Status: Bound- A
VolumeHandlepointing at an EBS volume ID (vol-…)
In the AWS console, confirm an EBS volume exists in the same region and AZ as the scheduled pod.
Admin → Infrastructure → Volumes in orka'i also shows storage_class on each PVC.
Volume expansion
If the StorageClass has allowVolumeExpansion: true (recommended), you can grow PVCs from Admin → Infrastructure → Volumes or by increasing the size on an app volume and redeploying. EBS supports online expansion when the CSI driver and StorageClass allow it.
Size decreases are not supported by Kubernetes or EBS.
Operational notes
| Topic | Implication |
|---|---|
| Access mode | orka'i uses ReadWriteOnce only — one node per volume |
| Multi-replica apps | Each replica needs its own volume, or use EFS for shared storage |
| Unmount vs delete | Unmounting from an app keeps the PVC; delete it from Admin → Volumes when no longer needed |
| AZ placement | RWO EBS is AZ-scoped; pods must schedule in the volume's AZ. WaitForFirstConsumer helps |
| Reclaim policy | Controlled by the StorageClass (Delete vs Retain). Delete removes the EBS volume when the PVC is deleted |
Troubleshooting
PVC stays Pending
- CSI driver pods not running:
kubectl get pods -n kube-system -l app.kubernetes.io/name=aws-ebs-csi-driver - IAM permissions missing on the CSI service account (EKS IRSA) or node instance role (K3s)
- No StorageClass named
gp3(or whatever you configured in orka'i) - Insufficient EBS quota in the AWS account
Pod cannot mount volume
- Pod scheduled in a different AZ than the EBS volume
- Node lacks permission to attach volumes (
ec2:AttachVolume)
orka'i still uses local-path
- Deploy target config not set or API not restarted after the SQL update
- Check:
SELECT config FROM deploy_targets WHERE is_default = true; - PVC was created before config was applied — delete and recreate (data loss unless you snapshot first)
Other cloud providers
The same orka'i mechanism applies to any CSI-backed StorageClass — only the class name and cluster prerequisites change:
| Provider | Example StorageClass | CSI provisioner |
|---|---|---|
| AWS EBS | gp3 | ebs.csi.aws.com |
| GCP Persistent Disk | pd-ssd | pd.csi.storage.gke.io |
| Azure Disk | managed-csi | disk.csi.azure.com |
Set default_storage_class in the deploy target config to the class name defined in your cluster.
Related
- App volumes: Project → App → Volumes tab
- Cluster-wide PVC list: Admin → Infrastructure → Volumes
- Local development uses node-local storage; EBS requires EC2-backed nodes in AWS