For the full CLI reference, YAML examples, and complete environment variable list see README.md.
| Tool | Required for |
|---|---|
| Go 1.21+ | Building binaries (Options A and B) |
| Docker | Building the container image (Options B and C) |
kubectl + kind |
Option C only |
Required for Option A; also needed if you want to run the CLI locally with Options B or C.
# Build binary for CLI
go build -o cloudstackctl main.go
# Build binary for Controller (if run locally)
go build -o cloudstackctl-controller cmd/controller/main.go
# Install to PATH
sudo mv cloudstackctl /usr/local/bin/Required for Options B and C. Build once before running either:
sudo docker build -t cloudstackctl:local .Copy the provided example env files and fill in your values:
cp .env.cloudstack.example .env.cloudstack
cp .env.database.example .env.database
# edit both files with real values.env.cloudstack template:
CLOUDSTACK_ENDPOINT=http://10.20.30.40:8080/client/api
CLOUDSTACK_API_KEY=
CLOUDSTACK_SECRET_KEY=
VERIFY_SSL=falseThese files are ignored by git. The CLI auto-loads .env.cloudstack when present, or accepts -c <file> to specify a different path. For the full list of supported environment variables see the Environment Variables Reference in README.md.
| Option | Best for |
|---|---|
| A — Local binaries | Simplest setup; no Docker required |
| B — Docker / docker-compose | Fast local setup using containers |
| C — kind / Kubernetes | Test Kubernetes behaviours or deploy to a production cluster |
Run PostgreSQL and the controller directly as local processes.
1. Start PostgreSQL
Use an existing local PostgreSQL instance, or run one in a container:
docker run -d --name cloudstackctl-postgres -p 5432:5432 \
-e POSTGRES_PASSWORD=secret -e POSTGRES_DB=cloudstackctl postgres:152. Export credentials
# CloudStack
source .env.cloudstack
# Database
export DATABASE_DSN="host=localhost user=postgres password=secret dbname=cloudstackctl port=5432 sslmode=disable"3. Start the controller
./cloudstackctl-controllerThe controller creates/migrates its database tables on startup and listens on :65426.
4. Use the CLI
./cloudstackctl apply -f application.yaml
./cloudstackctl get ApplicationQuick start
sudo docker-compose upThis starts postgres and cloudstackctl-controller using docker-compose.yml at the project root. The image contains both the cloudstackctl (CLI) and cloudstackctl-controller binaries.
Run containers individually
# Postgres
sudo docker run -d --name cloudstackctl-postgres -p 5432:5432 \
-e POSTGRES_PASSWORD=secret -e POSTGRES_DB=cloudstackctl postgres:15
# Controller
sudo docker run -d --name cloudstackctl-controller \
--link cloudstackctl-postgres:postgres -p 65426:65426 \
-e DATABASE_DSN='host=postgres user=postgres password=secret dbname=cloudstackctl port=5432 sslmode=disable' \
-e CLOUDSTACK_ENDPOINT='https://your-cloudstack-api.com/client/api' \
-e CLOUDSTACK_API_KEY=your-api-key \
-e CLOUDSTACK_SECRET_KEY=your-secret-key \
cloudstackctl:local /cloudstackctl-controllerUsing env files
sudo docker run -d --name cloudstackctl-postgres -p 5432:5432 \
-e POSTGRES_PASSWORD=secret -e POSTGRES_DB=cloudstackctl postgres:15
sudo docker run -d --name cloudstackctl-controller \
--link cloudstackctl-postgres:postgres -p 65426:65426 \
--env-file .env.cloudstack --env-file .env.database \
cloudstackctl:local /cloudstackctl-controllerNotes:
- Use bind mounts or Docker secrets to avoid exposing secrets in env vars.
- The CLI connects to
http://localhost:65426by default; override withCONTROLLER_ENDPOINT. - The controller logs to
/var/log/cloudstackctl-controller.logby default; override withCONTROLLER_LOG_FILE.
Install kind and kubectl (if not already present)
# kind (Linux x86_64)
curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64
chmod +x ./kind && sudo mv ./kind /usr/local/bin/kind
# kubectl
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl && sudo mv kubectl /usr/local/bin/Create a cluster and load the image
kind create cluster --name cloudstackctl
kubectl cluster-info --context kind-cloudstackctl
kind load docker-image cloudstackctl:local --name cloudstackctlCreate Secrets
kubectl create secret generic cloudstack-secret --from-env-file=.env.cloudstack
kubectl create secret generic database-secret --from-env-file=.env.databaseDeploy
Example manifests are in examples/k8s/:
| Manifest | Purpose |
|---|---|
cloudstack-secret.yaml |
CloudStack credentials (CLOUDSTACK_API_KEY, CLOUDSTACK_SECRET_KEY, CLOUDSTACK_ENDPOINT, VERIFY_SSL) |
database-secret.yaml |
Database credentials (DATABASE_DSN or PG* values) |
postgres-deployment.yaml |
PostgreSQL Deployment + Service |
controller-deployment.yaml |
Controller Deployment |
kubectl apply -f examples/k8s/cloudstack-secret.yaml
kubectl apply -f examples/k8s/database-secret.yaml
kubectl apply -f examples/k8s/postgres-deployment.yaml
kubectl apply -f examples/k8s/controller-deployment.yamlVerify
kubectl get pods -n default
kubectl logs deployment/cloudstackctl-controller -n defaultInject credentials via envFrom (recommended)
All settings are sensitive — use Kubernetes Secrets rather than ConfigMaps:
containers:
- name: cloudstackctl-controller
image: cloudstackctl:local
envFrom:
- secretRef:
name: cloudstack-secret
- secretRef:
name: database-secretThe controller also supports loading credentials directly from the Kubernetes API (looks up the Secret named by CLOUDSTACK_SECRET_NAME in CLOUDSTACK_SECRET_NAMESPACE). Use whichever approach fits your environment.
Cleanup
kind delete cluster --name cloudstackctlThe controller creates and migrates its database tables on startup (idempotent, via GORM AutoMigrate):
| Table | Stores |
|---|---|
applications |
Application resources |
components |
Component resources |
vm_specs |
VirtualMachineSpec resources |
virtual_machines |
VirtualMachine desired/observed records |
CloudStack-managed resources (Network, Volume, SSHKey, SecurityGroup, AffinityGroup, UserData) are not persisted in the database — they are managed directly via the CloudStack API.
If explicit migration/version control is needed, add a migration tool alongside GORM's AutoMigrate.