Getting Started
This guide creates an AWS cluster and deploys an example "hello" app on your cluster via the podmin CLI.
Prerequisites
Install Podmin with Homebrew:
brew install podmin-dev/tap/podmin
Or install the latest release with Go:
go install github.com/podmin-dev/podmin@latest
Prebuilt binaries are available from Podmin releases. You also need:
- OpenTofu 1.11 or Terraform 1.11 or newer. Podmin prefers OpenTofu when both are installed. The workload and cluster coordination CAs are created directly in Parameter Store and never enter OpenTofu/Terraform state.
- AWS CLI credentials allowed to create the required infrastructure.
Confirm your AWS identity:
aws sts get-caller-identity --profile=demo
Connect to AWS
You will need to decide on a cluster ID, AWS region, and globally unique S3 bucket name.
For this guide, the examples use:
- Cluster ID:
example - Region:
us-west-2 - Bucket Name:
example-podmin.
First, you need to create a local "context" - this will create the cluster bucket if it does not already exist:
podmin connect example \
--provider aws \
--region us-west-2 \
--bucket example-podmin
Podmin stores contexts under the applicable XDG config directory, falling back to ~/.podmin.
podmin connect selects the new context automatically. Run podmin use to list connected contexts or podmin use <cluster-id> to select one.
Create the Cluster
Create one default NodeGroup containing one ARM64 VM:
podmin setup \
--vpc-cidr 10.0.0.0/16 \
--nodegroup default
Review the OpenTofu/Terraform plan before approving it. Setup creates or reuses a compatible VPC, compares runtime dependencies with the cluster manifest, transfers only the pending files and images, and starts the NodeGroup. It is safe to run again for upgrades or configuration changes, including from CI without a persistent local cache.
You can add/remove NodeGroups at any time. Add multiple by repeating --nodegroup:
podmin setup \
--vpc-cidr 10.0.0.0/16 \
--nodegroup default \
--nodegroup workers,size=3,disk-size=100,instance-type=c8g.large,zone=b
NodeGroups use a 20 GiB root disk and the region's first available zone by default. Add disk-size=GIB to select an 8-16384 GiB root disk independently for a group. Add zone=b for the region's b zone or provide a full available AWS zone name.
The complete NodeGroup list is authoritative. Removing a NodeGroup from the command removes it after plan approval.
Optional Container Log Export
Podmin can run Fluent Bit on every node and export container stdout and stderr to an OTLP logs endpoint. If the provider requires headers, first store a JSON object in the context's default secrets provider, for example otel-logs-headers.json:
{
"Authorization": "Basic <base64-credentials>",
"stream-name": "podmin"
}
podmin secret create otel-logs-headers \
--system \
--file otel-logs-headers.json
podmin setup \
--vpc-cidr 10.0.0.0/16 \
--nodegroup default \
--otel-logs endpoint=https://api.openobserve.ai/api/example/v1/logs,headers-secret=true
OTLP/HTTP with protobuf is the default (grpc=false). Specify grpc=true for a secure gRPC endpoint without a URL path. headers-secret=true uses the explicitly user-manageable otel-logs-headers system secret stored at /<cluster>/_system/otel-logs-headers in the context's default secrets provider.
For an exporter endpoint that requires mutual TLS, enable a renewable workload identity and publish its issuing trust bundle where the provider can consume it:
podmin setup \
--vpc-cidr 10.0.0.0/16 \
--nodegroup default \
--otel-logs endpoint=https://logs.example.com/insert/opentelemetry/v1/logs,ca=s3://observability-trust/tls/logs-server-ca.pem,mtls=true \
--workload-ca-publish s3://observability-trust/podmin/workload-ca.pem
The destination bucket must already exist. On AWS, Podmin grants its generated node role and S3 endpoint read-only access to the exact server CA object and read/write access to the exact workload CA object. A bucket policy in another account and any customer-managed KMS key policy must also permit those operations. Configure the proxy in front of the logging provider, or the logging provider itself, to trust the published PEM bundle and, if desired, authorize the node SPIFFE pattern spiffe://<cluster>.podmin.internal/system/otel-logs/node/*. This is designed to interoperate with MonVM.
Deploy an Application
Copy Podplane's multi-platform Hello image into the cluster image store under the short name hello:
podmin push ghcr.io/podplane/hello:v1.5.0 hello
Deploy it to the default NodeGroup with Podmin's default/built-in manifest:
podmin deploy hello --image hello --nodegroup default --service
The default/built-in manifest includes a DaemonSet. --service includes an opinionated TCP Service on port 443 targeting port 8443, with an HTTPS readiness probe at /healthz, and configures images that support TLS_CERT_FILE and TLS_KEY_FILE to serve the mounted Podmin workload certificate. Repeatable or comma-separated --port SERVICE:TARGET mappings replace that HTTPS default with custom TCP mappings; the first target receives a TCP readiness probe.
- You can customise the manifest (and Service ports) by specifying a manifest file using
-f(we'll cover this later in Custom Workloads).
The application is now available inside the cluster (from other Pods) at:
https://hello.default.svc.cluster.local
List the cluster's committed desired state at any time:
podmin list
NAME NAMESPACE NODEGROUP SERVICE ORIGIN
hello default default hello workload
Note: Podmin does not have a control plane, so the podmin list command only tells you the desired state configured in your object storage bucket - this inventory does not claim that the asynchronously reconciled Pod is running or healthy.
Next Steps
- Ingress Tunnels makes the Hello service available through a Cloudflare Tunnel.
- CLI Reference details every command and argument for the Podmin CLI.
- Custom Workloads covers custom DaemonSet manifests, Services, secrets, and multi-platform images.
- GitHub Actions automates cluster setup and application deployment.
Clean Up
When finished, you can remove the app you deployed from the cluster's desired state:
podmin delete hello --nodegroup default
Run podmin list again to confirm that the deployment is no longer committed. Nodes remove the static Pod asynchronously.
Remove compute and networking while retaining the cluster bucket and certificate authorities so the cluster can be recreated later:
podmin teardown
Permanently remove all cluster infrastructure, stored workloads, images, and certificate authorities, then disconnect the local context:
podmin destroy
Both commands show what they will remove and require confirmation before proceeding.