• Get started

    • Getting started
    • Concepts
    • Tour
  • Using shpyrd

    • Deploying
    • shpyrd.yaml
    • Resources
    • Databases and caches
    • Domains and exposure
    • Sign-in for your app
    • Teams, roles and security
    • AI assistants (MCP)
    • Logs
    • Dashboard
    • CLI reference
  • Running it yourself

    • Installation
    • Oracle Cloud (OKE)
    • AWS (EKS)
    • Extensions and sign-in
    • Platform backups
    • Architecture guide
  • Project

    • Design principles
    • Roadmap
    • How to contribute
    • Support the project
  1. Running it yourself
  2. AWS (EKS)
Add toClaudeClaude CodeClaudeClaudeOpenAICodexCursorCursorVisual Studio CodeVS Code

AWS (EKS)

Run shpyrd on Amazon EKS - the network, cluster and VPN from Terraform, then one command for the platform.

This page is for running shpyrd yourself, in your own AWS account. On shpyrd cloud the platform is run for you: see Getting started. The aws profile installs shpyrd on Amazon EKS with Network Load Balancers for the public and the internal front door, Let's Encrypt certificates, Route 53 automation, an in-cluster registry and network policy enforcement from the VPC CNI. The reference infrastructure lives in contrib/aws as Terraform, including an AWS Client VPN as the way into private parts of the platform; the platform itself is shpyrd cluster init.

What you get

Networka VPC (10.0.0.0/16) with two public subnets (load balancers, one NAT gateway per zone) and two private /19 subnets for nodes and pods (the VPC CNI gives pods VPC addresses)
ClusterEKS in API authentication mode (the Terraform caller is the first administrator), a private API endpoint (reachable over the VPN; a public one restricted to your address is opt-in), one managed node group on Amazon Linux 2023, standard support only
CredentialsEKS Pod Identity: IAM roles associated with the service accounts that need AWS (the load balancer controller, the EBS and EFS CSI drivers, ExternalDNS, cert-manager). No access keys are created or stored
Front doorsNetwork Load Balancers from the AWS Load Balancer Controller with pod targets: an internet-facing one on two Elastic IPs (static addresses for allow-lists and apex A records), an internal one for projects marked internal (Domains and exposure). DNS uses their hostnames as alias records
CertificatesLet's Encrypt; with the zone in Route 53, one wildcard certificate for every project hostname through cert-manager's Route 53 solver
Registrythe in-cluster registry with TLS from the platform CA
Isolationthe VPC CNI's own network policy agent enforces NetworkPolicy (no Calico needed)
StorageEBS gp3 (encrypted, 1 GiB minimum) with snapshots; EFS for shared volumes (Resources)
DNSoptional: a public zone in Route 53 managed by ExternalDNS, delegated from your registrar once
Accessoptional: an AWS Client VPN endpoint into the VPC, with a profile for the AWS VPN Client

Prerequisites

  • An AWS account and the aws CLI configured (a named profile or the environment).
  • Terraform 1.5+ or OpenTofu, kubectl.
  • A domain (or a subdomain of one) for the platform, for example aws.example.com, whose zone can live in Route 53.
  • The shpyrd CLI (Installation); the AWS VPN Client for the VPN.

1. Network, cluster and VPN

git clone https://github.com/shpyrd-io/shpyrdcd shpyrd/contrib/aws/terraformcp terraform.tfvars.example terraform.tfvars

Fill in terraform.tfvars:

profile = "default"          # aws CLI profile; "" for the environmentregion  = "us-east-1"name    = "shpyrd-prod"
kubernetes_version = "1.36"node_instance_type = "t3a.large"node_count         = 2# nat_gateway_per_az = false         # one shared NAT gateway instead of one per zone
dns_zone       = "aws.example.com"   # public zone in Route 53; "" for nonevpn            = true                # Client VPN endpoint + profile: the way to kubectlshared_storage = true                # EFS for shared volumes# api_public_access = true           # also a public API endpoint, restricted to your address
terraform initterraform apply          # about 15 minutes

terraform output next_steps prints the rest: the kubectl context script, where the VPN profile was written, the zone's name servers and the full shpyrd cluster init command.

2. Connect the VPN

The Kubernetes API is private: only the VPC and VPN clients reach it. Terraform generated a certificate authority, the server certificate (imported to ACM) and one client certificate, and wrote contrib/aws/terraform/<name>-vpn.ovpn. Import it in the AWS VPN Client (File > Manage Profiles > Add Profile) and connect. The tunnel is split: only the VPC range goes through it, and DNS goes to the VPC resolver so private names resolve.

Connected, you reach what the internet cannot: the API endpoint, the internal front door (projects marked exposure: internal, or the whole platform with --set SHPYRD_PLATFORM_EXPOSURE=internal). The profile is a credential; keep it with the Terraform state (it is git-ignored) and rotate it by tainting tls_private_key.vpn_client.

No VPN on that machine?

api_public_access = true adds a public API endpoint restricted to admin_cidrs (your address at apply time by default). It is also the way back in if the profile is lost: Terraform talks to the AWS control plane, not to Kubernetes, so terraform apply restores access in two minutes.

3. Reach the cluster

cd ..                    # contrib/aws./kubeconfig.sh          # writes the kubectl context eks-<name>kubectl --context eks-shpyrd-prod get nodes

4. Delegate the zone

With dns_zone set, Terraform created the zone in Route 53. Delegate it once from the parent zone at your registrar, with the name servers from terraform output dns_zone_nameservers:

aws  NS  ns-1211.awsdns-23.orgaws  NS  ns-1671.awsdns-16.co.ukaws  NS  ns-302.awsdns-37.comaws  NS  ns-713.awsdns-25.net

Nothing in the zone is written by Terraform or by hand: ExternalDNS publishes *.aws.example.com as an alias of the external load balancer as soon as the platform is up, and one record per internal hostname pointing at the internal one.

5. Install the platform

Terraform wrote every value the platform needs from the infrastructure into contrib/aws/terraform/<name>.vars — you never copy an identifier by hand. The command terraform output next_steps printed:

shpyrd cluster init --context eks-shpyrd-prod --profile aws --vars-file contrib/aws/terraform/shpyrd-prod.vars \  --set SHPYRD_ACME_EMAIL=you@example.com --enable auth-local

What the file carries, and where each value comes from:

ValueMeaningSource
SHPYRD_DOMAINthe platform's domaindns_zone
SHPYRD_AWS_CLUSTER, SHPYRD_AWS_REGION, SHPYRD_AWS_VPC_IDwhat the load balancer controller managesthe cluster; discovered from the cluster itself when absent
SHPYRD_AWS_LB_EIPS, SHPYRD_LB_IPthe public front door's Elastic IPs (allocation ids for the controller, addresses for the Domains card)the two aws_eip.lb
SHPYRD_EFS_IDthe file system behind shared volumesshared_storage
SHPYRD_DNS_PROVIDER, SHPYRD_DNS_ZONE_ID, SHPYRD_DNS_REGIONRoute 53 automationthe hosted zone

Flags and --set win over the file, so --platform-exposure internal (dashboard, sign-in and Grafana behind the internal load balancer, VPN only, apps public) or --set SHPYRD_REGISTRY_SIZE=50Gi go on the same command line. On a cluster you did not create with this Terraform, pass the values with --set; the three cluster facts are read from the cluster.

What happens, in order:

LevelComponents
rc0Prometheus Operator CRDs
rc1the AWS Load Balancer Controller, cert-manager (with ambient credentials for Route 53), the registry credential, the snapshot controller and the EBS snapshot class, the gp3 and shpyrd-efs storage classes
rc2Let's Encrypt issuers, the platform CA and trust bundle, ingress-nginx behind an internet-facing NLB on the Elastic IPs and the internal one behind an internal NLB (both with pod targets), the registry and the node trust for it, ExternalDNS
rc3kpack with the Paketo builder, kube-prometheus-stack, the wildcard certificate, the control-plane database
rc4the shpyrd server

The installer waits for the load balancer hostname, for shpyrd.<domain> to resolve on public resolvers and for the certificates. The summary at the end:

  Dashboard:  https://shpyrd.aws.example.com  Grafana:    https://grafana.aws.example.com  Registry:   in-cluster at 10.100.0.50:5000 (TLS from the platform CA, credential in Secret shpyrd-registry)  External LB:   k8s-ingressn-ingressn-015ed2971e-ae9f48a7eeaa7d9f.elb.us-east-1.amazonaws.com (ExternalDNS: *.aws.example.com)  Internal LB:   k8s-ingressn-ingressn-60fb9cbfd6-a7ec35b89089177e.elb.us-east-1.amazonaws.com (ExternalDNS: per host, exposure:internal)

Everything you passed is recorded in the cluster: later runs (brew upgrade shpyrd && shpyrd cluster init --context eks-shpyrd-prod --profile aws) need no flags.

6. First sign-in and first project

The same as on Oracle Cloud: the admin token bootstraps, then accounts and a platform-admin team (Oracle Cloud, steps 5 and 6):

shpyrd cluster dashboard --context eks-shpyrd-prodshpyrd users add you@example.com --name "You" --context eks-shpyrd-prodshpyrd teams create platform --platform-role platform-admin --member you@example.com --context eks-shpyrd-prodshpyrd projects create shop --context eks-shpyrd-prodshpyrd deploy --project shop --context eks-shpyrd-prod

Costs

At the defaults, on demand in us-east-1: the EKS control plane $0.10 per hour, two t3a.large nodes $0.15, two NAT gateways $0.09 plus data (nat_gateway_per_az = false halves it), two Network Load Balancers $0.045, the Client VPN association $0.10 plus $0.05 per connection; about $0.50 per hour all in. EBS gp3 $0.08 per GB-month, EFS by the space used, the zone $0.50 per month. A development cluster is created for a working session and destroyed after it.

Good to know

  • Addresses and hostnames. The public front door has two static Elastic IPs (the A-record targets for a zone apex, shown on the Domains card) and a DNS name that ExternalDNS uses for alias records; the internal one has a DNS name only. Load balancers are managed by the AWS Load Balancer Controller with pod targets; an ALB is not used because it would terminate TLS with ACM certificates, which does not fit per-domain certificates from cert-manager.
  • Network policy. The VPC CNI enforces it with its own agent; cluster init recognises it and installs nothing.
  • Snapshots are EBS snapshots, crash-consistent: shpyrd runs sync in the instances mounting the volume before taking one, so what the application had written is in the copy.
  • Existing clusters. The profile works on any EKS cluster that has the same add-ons and Pod Identity associations as the Terraform creates (CSI drivers, shpyrd-system/external-dns, cert-manager/cert-manager), and subnets tagged for the in-tree load balancer discovery.
  • Upgrading. brew upgrade shpyrd then shpyrd cluster init on the context.
  • Platform backups. contrib/aws/terraform/backups creates an S3 bucket that outlives the cluster; backup_bucket in the cluster root grants the platform's service account access through Pod Identity (no keys) and puts the target in the vars file. Nightly archives, shpyrd cluster backup now, shpyrd cluster restore on a new cluster: Platform backups.

Tear down

shpyrd cluster destroy --context eks-shpyrd-prod   # projects and their data, load balancers, diskscd contrib/aws/terraform && terraform destroy       # cluster, network, zone, VPNkubectl config delete-context eks-shpyrd-prod

The zone's delegation at the registrar is the one thing left to remove by hand. The backup bucket (contrib/aws/terraform/backups) is untouched: it is there to restore from; terraform destroy in that directory removes it when the archives are no longer wanted.

On this page

  • What you get
  • Prerequisites
  • 1. Network, cluster and VPN
  • 2. Connect the VPN
  • 3. Reach the cluster
  • 4. Delegate the zone
  • 5. Install the platform
  • 6. First sign-in and first project
  • Costs
  • Good to know
  • Tear down
  • Docs

shpyrd is open source under MPL-2.0, and in beta.