Skip to main content
Version: 0.5 (Next)

AWS Bootstrap Installation

Deploy Konstruct on an AWS EKS cluster. Three steps: create an EKS cluster, create a GitHub App, then run Terraform.

Prerequisites

  • AWS account with credentials configured (aws configure)
  • Terraform 1.5+
  • kubectl v1.30+
  • eksctl (install guide)
  • A domain you control (registrar access to update NS records)

Step 0: Create an EKS Cluster

If you already have an EKS cluster, skip to Step 1.

eksctl create cluster \
--name konstruct-mgmt \
--region us-west-2 \
--version 1.35 \
--with-oidc \
--nodegroup-name konstruct-nodes \
--node-type m5.xlarge \
--nodes 3 \
--nodes-min 3 \
--nodes-max 5 \
--managed

This takes ~15-20 minutes.

Cluster requirements:

  • Minimum 3x m5.xlarge nodes (the platform deploys ~10 services: ESO, cert-manager, ingress-nginx, external-dns, Dex, and the Konstruct platform with 6 operators)
  • OIDC provider enabled (required for IRSA — see below)
  • No special VPC, subnet, or EBS CSI driver requirements

Once the cluster is ready, verify nodes:

kubectl get nodes
# Expect 3 nodes in Ready state

Enable the OIDC provider (required for IRSA, not enabled by default):

eksctl utils associate-iam-oidc-provider \
--cluster konstruct-mgmt --region us-west-2 --approve

Then record the OIDC endpoint for Terraform:

aws eks describe-cluster --name konstruct-mgmt --region us-west-2 \
--query 'cluster.identity.oidc.issuer' --output text

Step 1: Create a GitHub App

  1. Go to GitHub > Settings > Developer settings > GitHub Apps > New GitHub App
  2. Configure:
    • Name: konstruct-<your-domain> (must be unique across GitHub)
    • Homepage URL: https://konstruct.<your-domain>
    • Callback URL: https://konstruct.<your-domain>/api/v1/github-app/callback
    • Setup URL: https://konstruct.<your-domain>/api/v1/github-app/callback
    • Check "Request user authorization (OAuth) during installation"
    • Check "Redirect on update"
    • Webhook: Uncheck "Active" (not needed for bootstrap)
    • Permissions:
      • Repository: Administration (Read & write), Contents (Read & write), Metadata (Read-only)
      • Organization: Members (Read-only)
    • Where can this GitHub App be installed? Only on this account
  3. After creation, note:
    • App ID
    • Client ID
    • Client Secret (generate one)
  4. Generate a private key (downloads a .pem file)
caution

Do not install the GitHub App on your organization yet. That happens during onboarding in the Konstruct UI.

Step 2: Run Terraform

Terraform handles AWS infrastructure, platform services, and the Konstruct installation.

git clone https://github.com/konstructio/konstruct-aws.git
cd konstruct-aws/konstruct-bootstrap-job-role

Generate a bcrypt password hash

The dex_config.admin_password field requires a bcrypt hash of your desired login password:

htpasswd -nbBC 10 "" 'your-password' | tr -d ':\n' | sed 's/$2y/$2a/'

Copy the output (starts with $2a$10$...) — you'll use it in the tfvars below. Remember the plain text password — you'll need it to login to Konstruct as the kbot user.

Generate a shared OIDC secret

The oidc_client_secret in konstruct_api_config must match the secret in dex_config.static_clients[0]. Generate one:

openssl rand -hex 32

Configure terraform.tfvars

terraform.tfvars
cluster_oidc_endpoint = "https://oidc.eks.us-west-2.amazonaws.com/id/YOUR_OIDC_ID"
cluster_name = "konstruct-mgmt"
region = "us-west-2"
domain = "your-domain.com"
alerts_email = "alerts@your-domain.com"
git_provider = "github"

# DNS: "aws" for Route53 (default), "cloudflare" for Cloudflare
dns_provider = "aws"
# cloudflare_api_token = "your-token" # Required only if dns_provider = "cloudflare"

# Path to the .pem file downloaded in Step 1
github_app_private_key_file = "path/to/your-app.private-key.pem"

konstruct_api_config = {
admin_user = "kbot"
client_email = "admin@your-domain.com"
github_app_client_id = "Iv1.abc123" # From Step 1
github_app_client_secret = "your-client-secret" # From Step 1
github_app_id = "123456" # From Step 1
github_app_name = "konstruct-your-domain-com" # From Step 1
license_validator_url = ""
oidc_client_id = "konstruct"
oidc_client_secret = "YOUR_SHARED_SECRET" # From openssl command above
webhook_url = ""
}

dex_config = {
admin_password = "$2a$10$..." # bcrypt hash from htpasswd command above

# Azure AD (optional — leave empty if not using Azure SSO)
azure_client_id = ""
azure_client_secret = ""
azure_tenant_id = ""

static_clients = [
{
id = "konstruct"
name = "Konstruct"
public = false
redirectURIs = [
"https://konstruct.your-domain.com/api/v1/auth/callback"
]
secret = "YOUR_SHARED_SECRET" # Must match oidc_client_secret above
}
]
}

Apply

terraform init
terraform plan # Review what will be created
terraform apply
tip

Open a second terminal and watch pods come up: kubectl get pods -A -w

This creates:

  • IRSA roles for all platform services (ESO, cert-manager, external-dns, konstruct operators)
  • SSM parameters for secrets
  • Route53 hosted zone (if dns_provider = "aws") or Cloudflare ExternalSecret
  • S3 state store bucket
  • EKS access entries for Argo CD and Atlantis
  • Namespaces: dex, konstruct-system, kubefirst
  • CRDs: helmtemplates, pipelinetemplates
  • Helm releases: External Secrets Operator, Ingress NGINX, Cert Manager, External DNS, Dex, Konstruct (chart version 0.4.0-rc.551eae9e)
  • ClusterSecretStore (konstruct-aws-ssm), ClusterIssuer (letsencrypt-prod)
  • ExternalSecrets for Dex config and API secrets
  • kubefirst-initial-state secret for the bootstrap operator

Step 3: Configure DNS

After terraform apply completes, you must delegate your domain to the Route53 nameservers so that TLS certificates can be issued and services are reachable.

If using Route53 (dns_provider = "aws")

  1. Get the nameservers from the Terraform output:

    terraform output route53_nameservers
  2. Go to your domain registrar and update the NS records for your domain to the four nameservers shown (e.g., ns-1234.awsdns-56.org, ns-789.awsdns-01.co.uk, etc.)

  3. Verify DNS delegation is propagating:

    dig NS your-domain.com

    You should see the AWS nameservers in the response. Full propagation can take 15 minutes to 48 hours depending on your registrar.

  4. Check that cert-manager can issue certificates:

    kubectl get certificates -A
    kubectl get challenges -A

    Certificates will stay in a Pending state until DNS propagation completes. This is expected.

  5. If certificates remain Pending after DNS has propagated (confirmed via dig), the cluster's CoreDNS may have a stale cache. Restart it:

    kubectl rollout restart deployment coredns -n kube-system

    Then watch certificates resolve:

    kubectl get certificates -A -w

Confirming the full chain

Use this sequence to verify each layer is working:

# 1. Confirm NS delegation is live (should return awsdns nameservers)
dig NS your-domain.com +short

# 2. Confirm Route53 is answering directly
dig SOA your-domain.com @ns-YOUR-NAMESERVER.awsdns-XX.com +short

# 3. Confirm cert-manager challenges are progressing
kubectl get challenges -A

# 4. If challenges show SERVFAIL, restart CoreDNS (stale cache)
kubectl rollout restart deployment coredns -n kube-system

# 5. Confirm certificates are issued
kubectl get certificates -A

If using Cloudflare (dns_provider = "cloudflare")

Cloudflare manages DNS automatically via the API token you provided. No manual delegation is needed, but verify the zone is active in your Cloudflare dashboard.

warning

TLS certificates and ingress URLs will not work until DNS is fully propagated. If you see certificate errors, run kubectl describe challenges -A to see the specific failure reason.

Step 4: Login and Onboard

Allow time for all services to become ready. Expect:

  • NLB provisioning: 3-5 minutes after apply
  • DNS propagation: 15 minutes to 48 hours
  • TLS certificate issuance: a few minutes after DNS resolves

Check readiness:

# All platform pods running
kubectl get pods -n konstruct-system

# Ingress has an external address
kubectl get svc -n ingress-nginx

# TLS certificate is ready
kubectl get certificates -n konstruct-system

Once the certificate shows Ready = True:

  1. Open https://konstruct.<your-domain>
  2. Login with username kbot and the plaintext password you hashed in Step 2
  3. Click Connect with GitHub and install the GitHub App on your organization
  4. You're on the dashboard — Konstruct is ready

What happens automatically behind the scenes:

  1. The API stores the GitHub App connection and creates a bootstrap Project
  2. The team-management-operator hydrates a GitOps repository with platform templates
  3. Argo CD is installed and takes over management of all platform components

Verify

# All pods running
kubectl get pods -n konstruct-system

# After onboarding completes, ArgoCD manages everything
kubectl get pods -n argocd
kubectl get applications -n argocd

# Check the gitops repo was created in your GitHub org

Troubleshooting

Pods stuck in CrashLoopBackOff

Check logs for the failing pod:

kubectl logs -n konstruct-system <pod-name> --previous

Common causes: missing SSM parameters (check kubectl get externalsecrets -A), IRSA role misconfiguration.

Certificates stuck in Pending

kubectl describe challenges -A

SERVFAIL errors: If challenges show rcode was expected to be 'NOERROR' or 'NXDOMAIN', but got 'SERVFAIL', but dig NS your-domain.com returns the correct AWS nameservers, the cluster's CoreDNS has a stale cache:

kubectl rollout restart deployment coredns -n kube-system

No SERVFAIL: DNS delegation hasn't propagated yet. Verify with dig NS your-domain.com +short and wait.

"kubefirst-initial-state secret not found"

The secret is created by Terraform in the kubefirst namespace. Verify:

kubectl get secret kubefirst-initial-state -n kubefirst

What's Next?

  1. Configure cloud accounts
  2. Create your first cluster