Three ways to run devctl depending on your situation:
| Option | When to use | Requires |
|---|---|---|
| A — Full Kubernetes | Primary setup, closest to production | Docker, Terraform, kubectl |
| B — Docker Compose | Test API against an existing cluster | Docker, existing kubeconfig |
| C — Local dev | Backend hot-reload during development | Go 1.23+, existing cluster |
Install these before anything else:
# Verify each is available
terraform version # need 1.5+
kubectl version # need 1.28+
docker version # need 20+
go version # need 1.23+ (Option C only)| Tool | Install |
|---|---|
| Terraform | brew install terraform |
| kubectl | brew install kubectl |
| Docker Desktop | https://docs.docker.com/get-docker/ |
| Go | brew install go |
| kind | Installed automatically by Terraform |
This is the full setup. Terraform provisions a Kind cluster, ingress-nginx is installed via Helm, and the devctl backend runs as a pod inside the cluster.
git clone https://github.com/abhaychaurasiya/dev-env-platform.git
cd dev-env-platform./scripts/setup.shThis runs 5 steps automatically:
==> [1/5] Checking prerequisites...
==> [2/5] Provisioning Kind cluster via Terraform...
==> [3/5] Waiting for ingress-nginx to be ready...
==> [4/5] Deploying devctl backend...
==> [5/5] Setting up port-forward for local access...
What it does under the hood:
terraform init && terraform apply— creates a Kind cluster with ports 80/443 mapped to localhost- Installs
ingress-nginxvia Helm chart v4.9.1 into the cluster - Applies
k8s-manifests/backend/— RBAC, ServiceAccount, Deployment, Service - Applies
k8s-manifests/system/— the TTL cleanup CronJob
Expected output at the end:
==> Setup complete!
Next steps:
1. Install CLI: ./scripts/install-devctl.sh
2. Spin up env: devctl up --template=mern
./scripts/install-devctl.sh
devctl version
# devctl v0.1.0This builds the Go binary and copies it to /usr/local/bin/devctl.
In a separate terminal, keep this running:
kubectl port-forward -n devctl-system svc/devctl-backend 8080:8080You can verify it's working:
curl http://localhost:8080/healthz
# {"status":"ok"}devctl up --template=mernThe CLI will print the local URL when ready. Add it to /etc/hosts so your browser can resolve it:
echo "127.0.0.1 $(whoami)-mern.dev.local" | sudo tee -a /etc/hosts
open http://$(whoami)-mern.dev.localdevctl status
# Environment: dev-abhay
# Phase: Ready
# URL: http://abhay-mern.dev.local
# Expires: 2024-01-15T11:00:00Z (in 28m)
#
# Services:
# ✓ mongodb Running
# ✓ express Running
# ✓ react RunningWatch live (refreshes every 5s):
devctl status --watchDestroy just one environment:
devctl downDestroy everything — all environments, system resources, and the Kind cluster:
./scripts/teardown.shRuns the backend in a container using your existing ~/.kube/config. The backend talks to whatever cluster your kubeconfig points to.
docker compose up -dThe backend mounts ~/.kube/config read-only. If your kubeconfig is in a non-standard location, set KUBECONFIG before running:
KUBECONFIG=/path/to/config docker compose up -dcurl http://localhost:8080/healthz
# {"status":"ok"}
curl http://localhost:8080/api/v1/templates
# [{"name":"go-api",...},{"name":"mern",...},{"name":"react",...}]export DEVCTL_BACKEND_URL=http://localhost:8080
devctl up --template=react
devctl status
devctl downOr run the CLI as a one-shot container (no local Go install needed):
docker compose --profile cli run cli up --template=react
docker compose --profile cli run cli status
docker compose --profile cli run cli downdocker compose downRun the backend directly on your machine. Best when you're actively changing backend code.
./scripts/dev.shThis runs go mod tidy && go run . in backend/. The backend uses your current kubeconfig automatically.
devctl backend listening on :8080
In another terminal:
export DEVCTL_BACKEND_URL=http://localhost:8080
devctl up --template=go-api# Backend unit tests (no cluster needed)
cd backend && go test -race ./...
# CLI unit tests
cd cli && go test -race ./...
# Full E2E integration test (requires running cluster + backend on :8080)
DEVCTL_BACKEND_URL=http://localhost:8080 ./scripts/integration-test.shBy default the backend accepts all requests. To enable token auth:
Backend — set the token secret in the Deployment:
# k8s-manifests/backend/deployment.yaml
env:
- name: DEVCTL_TOKEN
valueFrom:
secretKeyRef:
name: devctl-token
key: tokenkubectl create secret generic devctl-token \
--from-literal=token=mysecrettoken \
-n devctl-systemCLI — pass the token:
devctl up --template=mern --token=mysecrettoken
# or
export DEVCTL_TOKEN=mysecrettoken
devctl up --template=mernEnvironments auto-delete after their TTL expires. A CronJob checks every 5 minutes.
devctl up --template=mern # default: 30 minutes
devctl up --template=mern --timeout=1h # 1 hour
devctl up --template=mern --timeout=4h # 4 hours
# Manual teardown before TTL
devctl downEach user gets an isolated namespace. Multiple developers can use the same cluster simultaneously:
# Alice
devctl up --template=mern --username=alice
# → namespace: dev-alice, URL: http://alice-mern.dev.local
# Bob
devctl up --template=react --username=bob
# → namespace: dev-bob, URL: http://bob-react.dev.localEach namespace has its own ResourceQuota preventing one user from exhausting cluster resources.
Backend pod not starting:
kubectl get pods -n devctl-system
kubectl describe pod -n devctl-system -l app=devctl-backend
kubectl logs -n devctl-system -l app=devctl-backendEnvironment stuck in Provisioning:
# Check pod events in the user namespace
kubectl get pods -n dev-<username>
kubectl describe pod -n dev-<username> <pod-name>
# Or use the CLI
devctl status --name=<username>-<template>ingress-nginx not ready after terraform apply:
kubectl get pods -n ingress-nginx
kubectl wait --namespace ingress-nginx \
--for=condition=ready pod \
--selector=app.kubernetes.io/component=controller \
--timeout=120sPort-forward dropped:
# Restart port-forward
kubectl port-forward -n devctl-system svc/devctl-backend 8080:8080Terraform state issues:
cd terraform
terraform refresh
terraform plan # review before applyingFull reset:
./scripts/teardown.sh # deletes everything
./scripts/setup.sh # start freshlint-test-cli go vet + golangci-lint + go test -race (cli/)
lint-test-backend go vet + golangci-lint + go test -race (backend/)
validate-manifests kubeval on all k8s-manifests/*.yaml
build-cli cross-compile: linux/amd64, darwin/arm64
build-backend-image docker build (no push in CI)
git tag v1.0.0
git push origin v1.0.0This triggers:
- GoReleaser — builds CLI binaries for Linux/macOS/Windows (amd64 + arm64), creates GitHub Release with archives and checksums
- Docker — builds and pushes
ghcr.io/abhaychaurasiya/devctl-backendwith semver tags + SHA digest
# Pull the released backend image
docker pull ghcr.io/abhaychaurasiya/devctl-backend:v1.0.0