DocumentationKubernetes from scratch (preview)
Docs/Kubernetes from scratch (preview)

Kubernetes from scratch — reuse your control plane

Give employees access to one private Kubernetes application without exposing it to the public internet. Start with a fresh cluster and fresh gateway identity; reuse your existing Tunnex control plane (CP), organization and licence.

Existing Tunnex CP ── identity, configuration and access policy
Employee's Tunnex client ── encrypted tunnel ── Kubernetes gateway
private Service VIP + DNS
Nginx application pods

The CP coordinates access; application packets do not normally pass through the CP. The gateway programs the selected service path. Tunnex’s exposed-service VIP and FQDN remain the employee-facing destinations when backend pod IPs change.

This tutorial first uses one gateway and one private application. Add a second gateway only after that path works. A separate entry gateway, an NLB, Route 53 hosted zone and Route 53 Resolver inbound endpoint are not required for this tutorial’s Tunnex-managed Kubernetes names. Existing corporate DNS zones are a different integration; see Private DNS architecture.

These screenshots were captured on 6 September 2026 from the live Tunnex AWS engineering sandbox, not a mockup. They illustrate the CP steps, not a second fresh installation. Forms labelled unsaved were cancelled; no resources or grants were created for this capture. AWS and CLI steps remain copyable commands.

The existing sandbox uses s205-aws-eks, gateways tunnex-s205-a3/b2, and s205-private-nginx on TCP 8080. This fresh tutorial uses engineering, engineering-k8s-a/b, and finance-nginx on TCP 80. Use your own assigned values, not the image’s addresses or versions. The sandbox’s extra entry gateway is not a basic-install prerequisite.

1. Prepare the existing CP and installation workstation

Section titled “1. Prepare the existing CP and installation workstation”
  1. Sign in to your existing CP. Do not run the CP installer again or reset its database.
  2. Select the organization you intend to use. Confirm the account has gateway enrollment, Kubernetes management and access-policy permissions.
  3. For group-based service grants and HA, confirm the required Enterprise/Scale entitlement in Settings → Licence & plan. A licence does not enable HA or policy enforcement automatically.
  4. In Sites, create a dedicated site, such as Engineering Kubernetes, or deliberately select an existing site. Record its name.
  5. Install the compatible Tunnex CLI on a supported administrative workstation. For a Linux-only preview bundle, use a Linux administration host with browser device-code login. The employee can still use a supported desktop client.
  6. Install AWS CLI, eksctl, kubectl, Helm 3.14 or newer and curl on the administration workstation. Authenticate AWS using your organization’s normal SSO/profile workflow; do not put access keys in this tutorial’s files.
Terminal window
aws sts get-caller-identity
tunnex version
tunnex k8s --help
kubectl version --client
helm version --short
tunnex login --server https://vpn.example.com

Replace https://vpn.example.com with your existing CP URL. On a headless administration host, add --device to the login command.

Checkpoint: the AWS account and Tunnex organization are the intended ones; the CLI exposes plan, install, status and diagnostics. Your CP’s trusted HTTPS API and configured agent-mTLS listener must be reachable from the new workers. Do not disable certificate verification to make enrollment succeed.

Live Add Site dialog with Engineering Kubernetes entered

Step 1 — Unsaved example. Create your site, then verify it appears in Sites; opening this dialog alone does not create it.

2. Create a fresh AWS Kubernetes environment

Section titled “2. Create a fresh AWS Kubernetes environment”

Use a new cluster name, VPC and worker group; do not reuse the development walk’s cluster, PVCs, gateway names or credentials. The example below is a small evaluation layout, not a production availability or sizing recommendation.

Before provisioning, check the region’s EC2 vCPU quota, allowed instance types and budget. EKS, worker instances, disks, public IPv4, traffic and any NAT/load balancers incur charges; free-tier eligibility is not a free-cost guarantee. Keep the existing CP out of the new cluster’s cleanup scope.

Save the following as engineering-eks.yaml. Replace ADMIN_PUBLIC_IP/32 with the administrator’s real public egress CIDR and verify that c7i-flex.large is available and permitted in your account. Select an EKS version supported by your Tunnex bundle; version selection is intentionally not pinned to an old example here. Review the generated configuration before creation.

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: engineering-tunnex-lab
region: ap-south-1
iam:
withOIDC: true
vpc:
clusterEndpoints:
publicAccess: true
privateAccess: true
publicAccessCIDRs: ['ADMIN_PUBLIC_IP/32']
nat:
gateway: Disable
managedNodeGroups:
- name: engineering-workers
instanceType: c7i-flex.large
desiredCapacity: 2
minSize: 2
maxSize: 2
privateNetworking: false
volumeSize: 30
volumeType: gp3
labels:
tunnex.io/lab: engineering
Terminal window
eksctl create cluster --config-file engineering-eks.yaml --dry-run
# Review the target, Kubernetes version, IAM and network plan before this write:
eksctl create cluster --config-file engineering-eks.yaml
aws eks update-kubeconfig --region ap-south-1 --name engineering-tunnex-lab
kubectl config current-context
kubectl get nodes -o wide

This lab puts EC2 workers in public subnets to avoid a NAT gateway and uses NodePort later. It does not make the application a public Service. Restrict security groups, do not open SSH or the whole NodePort range, and do not use this layout as your production network template. Public node addresses can change on replacement; production needs a separately validated stable endpoint design.

For private production workers, budget approved outbound connectivity to the CP and registry and use your platform team’s infrastructure template. Fargate, EKS Auto Mode and restricted/serverless node environments are not the verified host-network/privileged-manager path described here.

AWS references: cluster creation and API endpoint restrictions.

The gateway’s disk holds its identity, key and fencing state. An emptyDir is not a replacement. Install the Amazon EBS CSI add-on with its IAM permissions using AWS’s EBS CSI setup. Complete both the IAM-role and add-on steps; installing the driver alone is not enough.

For standard EC2-backed EKS, save tunnex-storage.yaml:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: tunnex-gp3
provisioner: ebs.csi.aws.com
parameters:
type: gp3
encrypted: 'true'
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Retain
allowVolumeExpansion: true
Terminal window
kubectl apply -f tunnex-storage.yaml
kubectl get storageclass tunnex-gp3
kubectl -n kube-system get pods -l app=ebs-csi-controller

Checkpoint: both Linux workers are Ready, the CSI controller is healthy and the named StorageClass exists. WaitForFirstConsumer may leave a claim Pending until a pod is scheduled; inspect events before calling that a failure. EBS is zonal: a retained volume constrains future scheduling. Retain also means an eventual PVC deletion can leave a separately billed disk.

3. Choose the gateway’s reachable endpoint

Section titled “3. Choose the gateway’s reachable endpoint”

From kubectl get nodes -o wide, choose one worker and record its Kubernetes hostname label and reachable public IP. Inspect labels with:

Terminal window
kubectl get nodes -L kubernetes.io/hostname

Use UDP NodePort 31081 for gateway A. In the exact security group attached to that worker, allow inbound UDP 31081 from your test employee’s public egress CIDR. If you later add another gateway or entry site, permit its required peer source as well. Keep the existing cluster-internal communication rules. Do not expose application port 80, gateway health endpoints or every NodePort to the internet. Ensure NACLs and workstation/firewall policy allow the UDP path.

The selected node must actually host this gateway because the Service uses local traffic handling. Save these non-secret shell variables, replacing every placeholder with your values:

Terminal window
ORG='YOUR_TUNNEX_ORGANIZATION_ID_OR_SLUG'
CONTEXT='YOUR_EXACT_KUBECTL_CONTEXT'
GATEWAY_HOSTNAME='YOUR_SELECTED_NODE_HOSTNAME_LABEL'
GATEWAY_ENDPOINT='YOUR_SELECTED_NODE_PUBLIC_IP:31081'
CHART_VERSION='YOUR_COMPATIBLE_PUBLISHED_OR_PREVIEW_CHART_VERSION'

For a private preview bundle, use the chart paths/OCI references and node image digest supplied in its manifest, adding --chart, --host-posture-chart and --image to both commands below. Authenticate the registry outside the command; workers also need pull permission. A workstation registry login does not give Kubernetes permission to pull images. Never paste registry passwords or a gateway join token into Helm values, Git or command arguments.

Terminal window
tunnex k8s plan \
--org "$ORG" --context "$CONTEXT" \
--node-name engineering-k8s-a --release engineering-a --namespace tunnex \
--chart-version "$CHART_VERSION" \
--host-posture-chart-version "$CHART_VERSION" \
--storage-class tunnex-gp3 \
--service-type NodePort --node-port 31081 --endpoint "$GATEWAY_ENDPOINT" \
--gateway-node-selector "kubernetes.io/hostname=$GATEWAY_HOSTNAME"
tunnex k8s install \
--org "$ORG" --context "$CONTEXT" \
--node-name engineering-k8s-a --release engineering-a --namespace tunnex \
--chart-version "$CHART_VERSION" \
--host-posture-chart-version "$CHART_VERSION" \
--storage-class tunnex-gp3 \
--service-type NodePort --node-port 31081 --endpoint "$GATEWAY_ENDPOINT" \
--gateway-node-selector "kubernetes.io/hostname=$GATEWAY_HOSTNAME" \
--yes

Read the redacted plan before approving it. The CLI installs/reuses the shared tunnex-host-posture release in tunnex-system, enrolls a unique gateway using a short-lived Secret, waits for real readiness and removes consumed bootstrap metadata. The host manager requires privileged admission; the gateway uses host networking and specific network capabilities. Do not bypass an admission refusal with manual sysctl, CNI, Secret or PVC patches.

Terminal window
tunnex k8s status --context "$CONTEXT" --release engineering-a --namespace tunnex
tunnex k8s diagnostics --context "$CONTEXT" --release engineering-a --namespace tunnex
kubectl -n tunnex get deployments,pods,services,pvc
kubectl -n tunnex-system get daemonset tunnex-host-posture

Checkpoint: the gateway is Ready and Online in the CP, its endpoint is the chosen public address and port, and its PVC is Bound. Record its gateway ID and PVC UID, not its private key. In the CP, assign/bind this gateway to the site from step 1 before registering the cluster. Do not move a shared production gateway between sites for the tutorial.

Live gateway inventory filtered to three healthy sandbox gateways

Step 4 — Existing healthy gateways. Your first installation needs only one. The displayed runtime version is enrollment metadata, not proof of the running image digest; verify readiness and installed versions with the commands above.

Open Kubernetes → Register cluster in your existing CP:

  1. Choose AWS → EKS, the Engineering Kubernetes site, and gateway engineering-k8s-a as the in-cluster connector.

  2. Name the Tunnex cluster engineering. This is its Tunnex name, not an AWS import.

  3. Read the real Kubernetes Service CIDR:

    Terminal window
    aws eks describe-cluster --region ap-south-1 --name engineering-tunnex-lab \
    --query 'cluster.kubernetesNetworkConfig.serviceIpv4Cidr' --output text
  4. Enter that value under advanced networking. Choose an unused synthetic VIP range, for example 100.96.20.0/24, only after checking for overlap with office, client, VPN, pod, Service and VPC ranges.

  5. Choose a dedicated private DNS suffix, for example engineering.internal.example.com, under a domain your organization controls. Do not reuse a suffix already owned by another resolver integration.

  6. Enroll and check that a connector is selected, not Connector required.

Registration is metadata and connector configuration; it does not provision AWS resources or grant every employee access.

Live Kubernetes inventory showing the existing EKS cluster and connector pool

Step 5 — Existing sandbox cluster. This preview’s summary says connector configuration 0 / 1 despite the configured pool shown on the card: the summary counter does not account for pool-backed connectors. Check the actual pool and gateway state; do not interpret that counter as installation proof.

Unsaved AWS EKS registration form with site and connector selected

Step 5 — Unsaved provider, site and connector selection. Select your newly installed gateway, not this sandbox’s gateway.

Unsaved registration advanced networking with synthetic range Service CIDR and DNS zone

Step 5 — Unsaved networking example. READY means the form is ready to submit, not that Kubernetes is installed. Verify the real Service CIDR and range overlap before enrolling.

Live cluster network state showing active pool DNS VIP and synthetic Service range

Step 5 — Existing cluster readback. After enrolling yours, verify its connector, DNS VIP, synthetic range, Service CIDR and suffix here.

Save as finance-demo.yaml. This creates a new namespace, two Nginx pods and a ClusterIP-only Service. For production, replace the example image with your approved digest-pinned image and use application TLS/authentication.

apiVersion: v1
kind: Namespace
metadata:
name: finance-demo
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: finance-nginx
namespace: finance-demo
spec:
replicas: 2
selector:
matchLabels:
app: finance-nginx
template:
metadata:
labels:
app: finance-nginx
spec:
containers:
- name: nginx
image: nginx:stable-alpine
ports:
- name: http
containerPort: 80
readinessProbe:
httpGet:
path: /
port: http
resources:
requests:
cpu: 50m
memory: 32Mi
limits:
cpu: 250m
memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
name: finance-nginx
namespace: finance-demo
spec:
type: ClusterIP
selector:
app: finance-nginx
ports:
- name: http
port: 80
targetPort: http
protocol: TCP
Terminal window
kubectl apply -f finance-demo.yaml
kubectl -n finance-demo rollout status deployment/finance-nginx --timeout=180s
kubectl -n finance-demo get service finance-nginx
kubectl -n finance-demo get endpointslices -l kubernetes.io/service-name=finance-nginx

Checkpoint: the deployment is Ready, the Service is ClusterIP (no external load balancer) and EndpointSlices contain ready backends. This checks Kubernetes health, not employee access yet.

7. Expose only the application’s port and grant Finance access

Section titled “7. Expose only the application’s port and grant Finance access”
  1. In Kubernetes → engineering → Expose service, select namespace finance-demo, Service finance-nginx and TCP 80 only.
  2. Copy the assigned VIP and FQDN from the exposed-service row. Do not use the pod IP or Kubernetes ClusterIP as the employee-facing destination.
  3. Create a Finance group under Access Policies → Groups and add your test employee. Manage organization membership under Users & Roles.
  4. In Access Policies, create a rule for that group targeting this exposed Kubernetes Service and its exact port. Do not grant the entire cluster, 0.0.0.0/0, the VPC CIDR or all ports to make a test pass.
  5. Use Test access, then follow the CP’s enforcement rollout for the selected site/gateway. Check effective policy for conflicting broader rules. A saved rule or simulation-only result is not proof that deny enforcement is active.

Live Expose service form selecting the private Nginx Service and TCP 8080

Step 7 — Unsaved exposure selection from the live connector inventory. The sandbox uses TCP 8080; select TCP 80 for this tutorial’s finance-nginx.

Live exposed Nginx Service row showing its synthetic VIP FQDN and TCP port

Step 7 — Existing exposure. After saving yours, copy its assigned VIP, complete FQDN and port from this row; verify them from the employee client in step 8.

Unsaved access rule targeting only the exposed Kubernetes Nginx Service

Step 7 — Unsaved rule example. The sandbox uses an individual test identity, ControlPlaneAdmin; choose your Finance group for the team workflow. This image does not demonstrate a Finance group grant.

Live access policies with enforcement on default deny and one Service allow rule

Step 7 — Existing sandbox enforcement state. Verify your own effective rules and run both permitted-user and ungranted-user checks; this screen alone is not proof of either traffic result.

Exposure and authorization are separate. A Tunnex Service grant controls its network destination and port, not individual URL paths. If /finance and /hr share the same host and port, enforce path-level authorization in the application or an authenticated reverse proxy, or use separate Services.

8. Connect an employee and prove IP plus DNS access

Section titled “8. Connect an employee and prove IP plus DNS access”

On the employee’s own laptop, install the compatible Tunnex desktop client, sign in to the existing CP and connect through the site/gateway from this guide. If the client instead enters through another gateway, that adds a separate transit path which must be configured and tested; do not silently assume it.

Set these to the actual values displayed in your CP:

Terminal window
SERVICE_VIP='YOUR_ASSIGNED_TUNNEX_VIP'
SERVICE_FQDN='YOUR_ASSIGNED_TUNNEX_FQDN'
curl --noproxy '*' --connect-timeout 5 "http://$SERVICE_VIP/"
curl --noproxy '*' --connect-timeout 5 "http://$SERVICE_FQDN/"

Both should return the Nginx welcome page. Open the FQDN URL in the browser too. HTTP is used only for this non-sensitive demo; the tunnel does not replace TLS between the gateway and application for production use.

Live capture limitation: the sandbox returned S20.5_PRIVATE_SERVICE_OK using normal curl by both VIP and FQDN on TCP 8080 during this capture. Its .app browser URL attempted HTTPS against the HTTP-only port and produced ERR_SSL_PROTOCOL_ERROR; there is no browser-success screenshot in this set. Use correctly configured application HTTPS for browser access and repeat that check. Do not bypass browser security or count the CP images as browser proof.

Tunnex supplies the private Kubernetes DNS path with the client configuration. You should not type a gateway DNS server into every application or replace your laptop’s global resolver. No Route 53 record is required for this synthetic name. Leave the application Service in place when pods are replaced: deleting and recreating a Service changes its identity and can require revalidation.

If FQDN fails but VIP works:

  • On macOS, check scutil --dns for the private suffix and use dscacheutil -q host -a name "$SERVICE_FQDN" plus normal curl/browser access.
  • On Linux with systemd-resolved, inspect resolvectl status and run resolvectl query "$SERVICE_FQDN".
  • On Windows PowerShell, use Resolve-DnsName with the copied name and curl.exe for the HTTP check.
  • nslookup/dig can query a default resolver instead of macOS’s scoped resolver. A public resolver’s NXDOMAIN alone does not diagnose the Tunnex path. An explicit query to the CP-displayed DNS VIP is a diagnostic only, not the final employee acceptance test.

Also test with a second employee who is not in Finance and has no other grant: application access should be denied. Test the permitted employee with the VPN disconnected: the synthetic VIP/name should not provide this path. Inspect access events; do not infer successful enforcement from DNS resolution.

Done for the basic setup: permitted user succeeds by VIP and FQDN, ungranted user is denied, and the application still has no public Service endpoint.

9. Optional: add a second gateway and test HA

Section titled “9. Optional: add a second gateway and test HA”

Use a different worker, gateway name, release, endpoint and PVC. Repeat steps 3–4 for engineering-k8s-b / engineering-b, UDP 31082, and that worker’s hostname/address. Do not schedule both host-network gateways on one worker or share their storage. Allow the required gateway-to-gateway UDP sources as well as employee traffic.

In the cluster’s Setup & diagnostics / connector pool controls, configure both members and deliberately enable HA after validating entitlement, eligibility and the reported effective mode. Helm installation does not create or activate the pool automatically. Check the saved membership list contains both IDs.

Live connector pool diagnostics showing enabled HA and effective fenced_ha mode

Step 9 — Existing pool reporting enabled HA and fenced_ha. This is a configuration snapshot, not a failover test. Verify membership, then collect traffic evidence across lease expiry using the controlled test below.

For a maintenance-window lab test, identify the current active member from fresh CP state, verify both gateways Ready and start paired VIP/FQDN requests. Scale only that member’s exact Deployment to zero; keep its PVC. Observe the new owner and continue traffic beyond the initial serving-lease expiry, then restore the stopped Deployment to one replica. Record outages, continued lease renewal, final readiness, identity and PVC UID. If using a script, ensure it restores the member on error. Never stop the CP or patch host networking as part of this check. Do not perform a destructive fault in customer production without an approved maintenance plan.

The development AWS proof observed automatic takeover in about 84 seconds and a separate failback interruption of about 50 seconds. Those are observations, not an SLA or seamless-failover guarantee. They do not qualify multi-AZ failure, host replacement or other clouds. HA is optional for the basic tutorial.

Use the typed CLI lifecycle, not raw token-bearing Helm commands. Check the installed release and target bundle before upgrade; review the plan and allow the CLI to wait for readiness:

Terminal window
tunnex k8s upgrade --context "$CONTEXT" \
--release engineering-a --namespace tunnex --chart-version "$CHART_VERSION"

Choose a genuinely intended target version; do not repeatedly upgrade to the same image as a repair. A rollback target must come from actual release history, not a guessed revision. Follow the matching bundle’s compatibility guidance.

Uninstall retains the gateway PVC by default:

Terminal window
tunnex k8s uninstall --context "$CONTEXT" \
--release engineering-a --namespace tunnex

To reinstall that identity, use install --mode reuse --existing-claim with the exact retained PVC name from inventory and the same reviewed endpoint, placement, organization and compatible bundle inputs from step 4. Never mint a fresh enrollment token against a used identity volume. If install is interrupted, use status/diagnostics and the typed retry or abort-install instruction printed by the CLI. Do not repeatedly mint tokens or edit lifecycle Secrets.

For lab cleanup, first remove the lab’s grants/exposure and resolve CP references, then uninstall the gateway releases. Stop here if you want identity reuse. Deleting the namespace, cluster, PVC or backing disk is not a harmless uninstall: record exact resources, obtain the appropriate approval and use the documented purge-state workflow where applicable before infrastructure cleanup. Retained EBS disks may continue billing. Never include the reused CP or unrelated VPCs, roles, hosted zones or employee data in a blanket cleanup command.

SymptomCheck before retrying
CLI has no k8s commandWrong/older bundle; do not provision more infrastructure
ImagePullBackOffWorker registry permission, exact image digest and existing pull-Secret name
Pending gateway PVCCSI IAM/add-on, StorageClass, selected node/AZ and scheduling events
Gateway not ReadyTyped diagnostics, trusted CP API/agent reachability and host-manager admission
Gateway Online but no private trafficCorrect connector/site, ready Service endpoints, actual grant enforcement and UDP endpoint
IP works, FQDN failsNormal client split-DNS suffix/VIP and possible conflicting corporate resolver
Traffic recovers once then stopsSustained lease renewal and exact deployed API version; do not call HA passed on one request
Install asks for manual host/PVC/Secret repairPreserve redacted diagnostics and escalate; that is not successful zero-touch installation

The optional GitOps operator is intentionally outside this first-app tutorial. Use only a compatible operator image plus gateway, host-posture, operator and CRD charts from the same approved bundle. The older Kubernetes reference’s operator warning applies to its older release, not proof that this preview is publicly shipped. CRD adoption and rollback require separate qualification.

Windows automatic terminal-crash recovery, failed private-candidate rollout recovery and ordinary host-reboot rebootstrap remain named follow-ups. The AWS proof used NodePort, not an NLB; AKS/GKE compatibility intent is not live qualification. Do not advertise these untested guarantees from this guide.

Documentation

Search Tunnex docs

Screenshot preview