stackctl
Los equipos pequeños y los proyectos indie merecen una herramienta de infraestructura sólida — sin la sobrecarga empresarial.
stackctl reúne gestión de configuraciones de Kubernetes, secretos de HashiCorp Vault y VPN NetBird en
una única CLI con interfaz consistente. El objetivo es permitir que un equipo pequeño opere de forma segura y con
confianza: los secretos nunca se exponen en texto plano, los kubeconfigs viven centralizados en Vault, el acceso a la
VPN está automatizado y todo puede ejecutarse dentro de un pipeline de CI/CD sin herramientas adicionales.
Ya seas un desarrollador en solitario, una startup o un pequeño equipo de operaciones, stackctl te da las mismas
prácticas de seguridad usadas a escala — sin la complejidad.
Instalar via curl (recomendado):
curl -fsSL https://eliasmeireles.com.br/tools/stackctl/install.sh | bash
Instalar una versión específica:
curl -fsSL https://eliasmeireles.com.br/tools/stackctl/install.sh | bash -s v0.0.9
Instalar desde el código (requiere Go):
go install github.com/eliasmeireles/stackctl/cmd/stackctl@latest
Actualizar una instalación existente:
# Última release estable (omite pre-releases por defecto)
sudo stackctl self-update
# Fijar una versión específica
sudo stackctl self-update --version v0.0.9
# Optar por un release candidate
sudo stackctl self-update --version v0.1.0-rc-05
Ejecuta stackctl sin argumentos para abrir la TUI interactiva.
Autenticación en Vault
Todos los comandos de Vault resuelven las credenciales en este orden:
| Prioridad | Origen |
|---|---|
| 1 | Flags de la CLI: --addr, --token, --role-id/--secret-id, --k8s-role |
| 2 | Variables de entorno: VAULT_ADDR, VAULT_TOKEN, VAULT_ROLE_ID, VAULT_SECRET_ID, VAULT_K8S_ROLE |
| 3 | Archivo ~/.vault-token (generado por vault login) |
| Método de auth | Requerido |
|---|---|
| Token | VAULT_ADDR + VAULT_TOKEN |
| AppRole | VAULT_ADDR + VAULT_ROLE_ID + VAULT_SECRET_ID |
| Kubernetes SA | VAULT_ADDR + VAULT_K8S_ROLE (+ opcional VAULT_K8S_MOUNT_PATH, VAULT_SA_TOKEN_PATH) |
Flags Globales
| Flag | Por defecto | Descripción |
|---|---|---|
--output / -o | table | Formato de salida: table, json, yaml |
Cuando se usa --output json o --output yaml, los emojis decorativos y los mensajes de progreso se omiten para que
la salida sea legible por máquina.
stackctl database postgres list --host localhost --admin-user postgres --admin-password secret --output json
stackctl vault secret list --output yaml
Contexto del Proyecto — stackctl context
Evita repetir --host, --port, --admin-user en cada comando guardando los valores por defecto en un archivo
.stackctl.yaml. El archivo se busca jerárquicamente desde el directorio actual hasta tu home.
# Crea .stackctl.yaml interactivamente en el directorio actual
stackctl context init
# Muestra la configuración activa
stackctl context show
Formato del .stackctl.yaml:
version: "1"
databases:
postgres:
host: localhost
port: 5432
user: postgres
vault-login: secret/databases/postgres/admin # opcional
mysql:
host: localhost
port: 3306
user: root
mongodb:
host: localhost
port: 27017
user: admin
messagebrokers:
rabbitmq:
host: localhost
port: 5672
user: guest
Nota: agrega
.stackctl.yamla tu.gitignore— puede contener contraseñas o paths de Vault.
Las flags explícitas en la CLI siempre sobrescriben los valores por defecto del contexto.
Comandos
TUI Interactiva
stackctl
Navega todas las funciones a través de un menú. Cada submenú muestra una nota contextual explicando el paso actual, y
las pantallas de entrada muestran el breadcrumb completo de navegación más un contador de pasos (step N of M).
Funciones interactivas principales:
- Navegación por paths de Vault — recorre el árbol KV de Vault para elegir credenciales de admin en lugar de escribir el path
- Auto-generar contraseña — escribe
autooauto:<tamaño>para generar una contraseña aleatoria (impresa al salir de la TUI) - Selección de base de datos — elige de una lista numerada de bases existentes o escribe un nombre nuevo
- KV engine ausente — se crea automáticamente cuando el destino de
--vault-pathaún no existe
Reintenta la autenticación de Vault cada 5 segundos si el token aún no está disponible.
Personalización de colores de la TUI (códigos de color ANSI 256):
| Variable | Por defecto | Controla |
|---|---|---|
STACK_CTL_TITLE_COLOR | 86 | Título del menú |
STACK_CTL_ITEM_COLOR | 86 | Items de la lista |
STACK_CTL_SELECTED_ITEM_COLOR | 82 | Item seleccionado |
Kubeconfig — stackctl kubeconfig
| Subcomando | Descripción |
|---|---|
list-contexts | Lista todos los contextos locales |
get-context <nombre> [--encode] | Imprime un contexto (opcionalmente en Base64) |
set-context <nombre> | Cambia el contexto actual |
set-namespace <ns> [--context <nombre>] | Define el namespace por defecto |
clean | Elimina entradas duplicadas |
add | Importa configuración (ver flags abajo) |
remove <nombre> | Elimina un contexto |
save-to-vault <nombre> | Sube el contexto a Vault |
add-from-vault <path> | Descarga y mezcla desde Vault |
contexts | Lista los kubeconfigs guardados en Vault |
from-sa | Construye un kubeconfig a partir de un token de SA |
apply -f <manifest> | Ejecuta un flujo descrito en un manifest YAML |
revert -f <manifest> | Deshace lo que hizo apply, usando el mismo manifest |
Flags de add:
| Flag | Descripción |
|---|---|
<base64> | Posicional: importa desde una cadena Base64 |
--file <path> | Importa desde un archivo local |
--host <ip> --ssh-user <user> | Importa por SSH |
--k3s | Usa el path por defecto de k3s (/etc/rancher/k3s/k3s.yaml) |
-r <nombre> | Renombra el contexto importado |
stackctl kubeconfig add --k3s --host 192.168.1.10 --ssh-user root -r home-lab
stackctl kubeconfig save-to-vault home-lab
stackctl kubeconfig add-from-vault secret/data/kubeconfig/home-lab
Flags de from-sa:
| Flag | Descripción |
|---|---|
--sa <nombre> | Nombre de la ServiceAccount (requerido) |
--namespace <ns> | Namespace donde reside la SA/Secret (por defecto kube-system) |
--secret <nombre> | Nombre del Secret del token (por defecto: <sa>-token) |
--cluster-name <nombre> | Nombre del cluster a embeber (por defecto: kubernetes) |
--context-name <nombre> | Nombre del contexto (por defecto: <sa>@<cluster-name>) |
--default-namespace | Namespace por defecto para el nuevo contexto |
--server <url> | Sobrescribe la URL del API server (por defecto: lee del kubeconfig activo) |
--kube-context <nombre> | Contexto kube desde el cual leer server/CA (por defecto: actual) |
--output-file <path> | Escribe el kubeconfig en un archivo en lugar de mezclar en el kubeconfig activo |
# Mezcla un kubeconfig para la SA dentro del kubeconfig activo
stackctl kubeconfig from-sa \
--sa <nombre-sa> --secret <token-secret> \
--cluster-name <nombre-cluster> \
--default-namespace <ns-defecto>
# O escribe en un archivo separado (útil para pasarle el kubeconfig a un compañero)
stackctl kubeconfig from-sa --sa <nombre-sa> --secret <token-secret> \
--output-file ./<nombre-sa>.kubeconfig
apply — flujos de kubeconfig basados en manifest
El discriminador kind selecciona el flujo. Actualmente soportado:
| Kind | Comando equivalente de la CLI |
|---|---|
KubeconfigFromSA | stackctl kubeconfig from-sa ... |
El manifest se valida antes de cualquier llamada al cluster — un kind ausente, un spec ausente o un campo
requerido del spec faltante aborta con un error claro.
# kubeconfig-from-sa.yaml
apiVersion: stackctl/v1
kind: KubeconfigFromSA
spec:
serviceAccount: dev-user # requerido
namespace: kube-system # por defecto: kube-system
secret: dev-user-token # por defecto: <serviceAccount>-token
clusterName: homelab # por defecto: kubernetes
contextName: dev-user@homelab # por defecto: <sa>@<clusterName>
defaultNamespace: homelab-dev # por defecto: default
# server: https://10.0.0.1:6443 # override opcional
# kubeContext: my-cluster # opcional, usa el actual por defecto
# outputFile: ./dev-user.kubeconfig # opcional; mezcla en el kubeconfig activo si está vacío
stackctl kubeconfig apply -f kubeconfig-from-sa.yaml # avanza
stackctl kubeconfig revert -f kubeconfig-from-sa.yaml # deshace usando el mismo archivo
revert usa el mismo switch de kind que apply. Para KubeconfigFromSA, elimina el contexto generado (y entradas
huérfanas de cluster/usuario) del kubeconfig activo, o borra el archivo apuntado por spec.outputFile cuando se
define. La operación es idempotente — un archivo o contexto ausente produce un aviso en lugar de un error.
Hay un ejemplo funcional en example/kubeconfig-from-sa.yaml.
Nuevos kinds se añadirán aquí a medida que se vayan lanzando.
Vault — stackctl vault
Secrets
stackctl vault secret list [path]
stackctl vault secret get <path>
stackctl vault secret put <path> clave=valor [clave=valor ...]
stackctl vault secret delete <path>
Path por defecto del listado: secret/metadata/resources/kubeconfig
Policies
stackctl vault policy list
stackctl vault policy get <nombre>
stackctl vault policy put <nombre> <archivo.hcl>
stackctl vault policy delete <nombre>
Métodos de auth
stackctl vault auth list
stackctl vault auth enable <tipo> [--path <path>] [--description <desc>]
stackctl vault auth disable <path>
Engines de secrets
stackctl vault engine list
stackctl vault engine enable <tipo> [--path <path>] [--description <desc>]
stackctl vault engine disable <path>
Roles
stackctl vault role list <auth-mount>
stackctl vault role get <auth-mount> <nombre>
stackctl vault role put <auth-mount> <nombre> [flags]
stackctl vault role delete <auth-mount> <nombre>
Flags de role put: --bound-sa-names, --bound-sa-namespaces, --policies, --token-policies, --ttl,
--token-max-ttl, --secret-id-ttl, --secret-id-num-uses
Apply declarativo
stackctl vault apply -f config.yaml # valida primero; aborta ante cualquier error
stackctl vault revert -f config.yaml # deshace lo aplicado
stackctl vault generate-manifest --all # genera una plantilla totalmente comentada
Aplica engines → auth → policies → roles → service_accounts → users → secrets → kubernetes en ese orden. La validación se ejecuta antes de cualquier cambio — todos los problemas de schema en el manifest se reportan de una vez para poder corregirlos en una sola pasada.
El bloque kubernetes: puede gestionar declarativamente:
| Recurso | Notas |
|---|---|
namespaces | Crea/actualiza con labels/annotations |
registry_secrets | Secrets dockerconfigjson, credenciales inline o tomadas de un path KV de Vault |
service_accounts | ServiceAccounts de Kubernetes con image pull secrets y control de automount |
secrets | Secrets genéricos (Opaque por defecto) — soporta kubernetes.io/service-account-token |
config_maps | ConfigMaps con data arbitrario |
role_bindings | RoleBinding namespaced a un Role o ClusterRole, múltiples subjects |
cluster_role_bindings | Binding a nivel de cluster a un ClusterRole |
Mira example/vault-config.yaml y example/homelab-rbac.yaml para referencias funcionales.
Fetch (CI/CD)
Obtiene un secret y lo mezcla como kubeconfig, o exporta los campos como variables de entorno.
Las flags de auth (--addr, --token, --role-id, etc.) se heredan del comando padre vault.
stackctl vault fetch \
--addr $VAULT_ADDR \
--role-id $VAULT_ROLE_ID --secret-id $VAULT_SECRET_ID \
--secret-path secret/data/ci/kubeconfig/prod \
-r prod-cluster
| Flag | Descripción |
|---|---|
--secret-path | Path KV v2 al secret |
--secret-field | Campo a leer (por defecto: kubeconfig) |
--as-kubeconfig | Mezcla el valor del campo (Base64) en el kubeconfig local (default) |
--export-env | Exporta todos los campos como variables de entorno |
--github-env | También escribe en $GITHUB_ENV |
-r | Renombra el contexto al importarlo |
Gestión de secretos — stackctl get secret
Obtiene secretos de Vault y los copia al portapapeles o los guarda en un archivo. El valor del secret nunca se imprime en la terminal.
Manejo de paths: todos los paths reciben automáticamente el prefijo secret/data/ para compatibilidad con KV v2.
# Copia un secret al portapapeles
stackctl get secret <KEY>
# Obtiene de un path personalizado (secret/data/ se prefija automáticamente)
stackctl get secret <KEY> --path resources/vps/elias-oracle
# Guarda en un archivo
stackctl get secret PUB_KEY --path resources/vps/elias-oracle --to-file ~/.ssh/id_rsa.pub
# Decodifica desde base64 antes de guardar
stackctl get secret ENCODED_KEY --path apps/production --to-file ./decoded.txt --decode-from-b64
# Reemplaza un archivo existente
stackctl get secret PUB_KEY --to-file ~/.ssh/id_rsa.pub --replace
Flags:
| Flag | Descripción |
|---|---|
--path <path> | Path de Vault (sin el prefijo secret/data/) |
--to-file <archivo> | Guarda el secret en archivo en vez del portapapeles |
--decode-from-b64 | Decodifica el secret desde base64 antes de guardar/copiar |
--replace | Reemplaza el archivo si ya existe (solo con --to-file) |
STACK_CTL_DEFAULT_SECRET_PATH | Variable para definir el path por defecto (sin prefijo secret/data/) |
| (path por defecto) | users/all/passwords (se vuelve secret/data/users/all/passwords) |
Comandos de gestión de contraseñas:
# Agrega una contraseña (auto-generada si --pass se omite; también se copia al portapapeles)
stackctl add pass <KEY> [--pass <valor>] [--size <bytes>]
# Actualiza una contraseña
stackctl update pass <KEY> [--pass <valor>] [--size <bytes>]
# Elimina una contraseña
stackctl delete pass <KEY>
Generate — stackctl generate
Genera contraseñas y nombres de usuario aleatorios, copiados automáticamente al portapapeles.
# Genera una contraseña aleatoria (copiada al portapapeles)
stackctl generate password
# Genera una contraseña de tamaño específico (bytes de entropía)
stackctl generate password --size 32
# Genera un username aleatorio
stackctl generate username
# Imprime el valor en lugar de copiarlo (útil en scripts)
stackctl generate password --output json
Cuando el portapapeles no está disponible (ej.: en CI/CD), el valor generado se guarda en ~/.stackctl/pass.
VPN NetBird — stackctl netbird
stackctl netbird install
stackctl netbird up --netbird-key <key> [--api-host <host>] [--wait-dns]
stackctl netbird status
| Variable | Descripción |
|---|---|
STACK_CLT_NETBIRD_KEY | Setup key |
API_HOST | Host de la API de gestión (por defecto: api.netbird.io) |
Gestión de Bases de Datos — stackctl database
Gestiona bases de datos, usuarios, schemas y prueba conexiones (PostgreSQL, MySQL, MongoDB).
Los comandos siguen una jerarquía que prioriza el tipo de base: stackctl database {postgres|mysql|mongodb} {list|create|delete|test} ...
# Lista bases de datos y usuarios
stackctl database postgres list \
--host localhost \
--admin-user postgres \
--admin-password secret
# Crea un usuario (auto-genera la contraseña; lista bases existentes interactivamente)
stackctl database postgres create user \
--host localhost \
--admin-user postgres \
--admin-password secret \
--username myapp_user \
--password auto \
--vault-path secret/databases/postgres/myapp_user
# Crea un usuario con contraseña y base de datos explícitas
stackctl database postgres create user \
--vault-login secret/databases/postgres/admin \
--username myapp_user \
--password myapp_pass \
--database myapp_db \
--vault-path secret/databases/postgres/myapp_user
# Elimina un usuario — omite --username para ver una lista numerada y seleccionar interactivamente
stackctl database postgres delete user \
--host localhost \
--admin-user postgres \
--admin-password secret
# Elimina un usuario específico directamente (pide confirmación por ser irreversible)
stackctl database postgres delete user \
--host localhost \
--admin-user postgres \
--admin-password secret \
--username old_user
# Elimina una base — omite --database para seleccionar de la lista; --force omite la confirmación
stackctl database postgres delete database \
--host localhost \
--admin-user postgres \
--admin-password secret \
--database old_db \
--force
# Prueba las credenciales de un usuario
stackctl database postgres test user \
--host localhost \
--username myapp_user \
--password myapp_pass \
--database myapp_db
Bases soportadas: PostgreSQL · MySQL · MongoDB
Mira DATABASE_COMMANDS.md para la referencia completa de comandos.
Gestión de Message Brokers — stackctl messagebroker
Gestiona usuarios y credenciales de message broker (RabbitMQ).
# Crea un usuario en RabbitMQ
stackctl messagebroker rabbitmq create user \
--host localhost \
--admin-user admin \
--admin-password secret \
--username myapp_user \
--password myapp_pass \
--tags "administrator,management" \
--vault-path secret/messagebroker/rabbitmq/myapp_user
# Lista todos los usuarios
stackctl messagebroker rabbitmq list user \
--host localhost \
--admin-user admin \
--admin-password secret
# Elimina un usuario — omite --username para ver una lista numerada y seleccionar interactivamente
stackctl messagebroker rabbitmq delete user \
--host localhost \
--admin-user admin \
--admin-password secret
# Prueba las credenciales del usuario
stackctl messagebroker rabbitmq test-user \
--host localhost \
--username myapp_user \
--password myapp_pass
Brokers soportados: RabbitMQ
Tags comunes de RabbitMQ: administrator · management · policymaker · monitoring
Mira MESSAGEBROKER_COMMANDS.md para la referencia completa de comandos.
Self-update — stackctl self-update
Reemplaza el binario en ejecución por el de la última GitHub Release. Las tags de pre-release se omiten por defecto;
usa --version para fijar una versión o para optar por un release candidate.
| Flag | Por defecto | Descripción |
|---|---|---|
--version | (última estable) | Tag específica para instalar. Salta el filtro de solo-estable. |
--install-path | /usr/local/bin/stackctl | Path a reemplazar (defínelo si stackctl vive en otro lugar). |
sudo stackctl self-update # última estable
sudo stackctl self-update --version v0.0.9 # fija una versión estable
sudo stackctl self-update --version v0.1.0-rc-05 # opta por un RC
stackctl self-update --install-path "$HOME/bin/stackctl"
La descarga cae en un archivo temporal junto al directorio de instalación cuando es posible (para rename atómico), con un fallback entre filesystems (EXDEV) que copia el binario de forma segura.
Ejemplo de CI/CD (GitHub Actions)
- name: Instalar stackctl
run: curl -fsSL https://eliasmeireles.com.br/tools/stackctl/install.sh | bash
- name: Conectar VPN
run: |
stackctl netbird install
stackctl netbird up --netbird-key ${{ secrets.NETBIRD_KEY }} --wait-dns
- name: Obtener kubeconfig
env:
VAULT_ADDR: ${{ secrets.VAULT_ADDR }}
VAULT_ROLE_ID: ${{ secrets.VAULT_ROLE_ID }}
VAULT_SECRET_ID: ${{ secrets.VAULT_SECRET_ID }}
run: |
stackctl vault fetch \
--secret-path secret/data/ci/kubeconfig/prod \
-r prod-cluster
- name: Deploy
run: kubectl apply -f k8s/
Tests
Ejecutar los tests
# Ejecuta todos los tests
make test
# Ejecuta los tests con cobertura
go test -cover ./...
# Ejecuta los tests de un paquete específico
go test ./cmd/stackctl/cmd/vault/...
Entorno de Desarrollo Local
Para tests de integración y desarrollo local, puedes levantar un entorno completo de Vault + Kubernetes usando Multipass:
# Bootstrap de un cluster k3s local con Vault
make multipass
# Esto crea una VM con:
# - cluster Kubernetes k3s
# - HashiCorp Vault (auto-inicializado y desbloqueado)
# - NGINX Ingress Controller
# - CLI stackctl pre-instalada
Mira .dev/multipass/README.md para
instrucciones detalladas de configuración y requisitos.
Contribuciones
¡Las contribuciones son bienvenidas! Siéntete libre de abrir un Pull Request.