Config and secrets

Who am I

sklp whoami answers in one call what an operator or an AI agent needs before acting: API login, projects, deploy contexts, stacks and their config keys. Run it first.

sklp whoami
sklp whoami --json

Reference: sklp whoami.

Authentication

sklp auth manages the CLI's token for the Skalpai API. Logging out removes the token, keeps the URL and leaves deploy contexts untouched: API login and deploy targets are separate.

sklp auth login --api-url https://api.skalpai.dev
sklp auth status
sklp auth logout

sklp project list lists the projects you can reach (--stack, --json, --plain). Commands that act on a project take --project, or read SKLP_PROJECT.

The CLI's own settings live in ~/.config/sklp/config.yaml, or in the file SKLP_CONFIG points at.

Stack config

Each stack keeps its configuration in .sklp/<stack>/config.yaml: plain values and encrypted secrets side by side. Secrets are AES-256-GCM, keyed by a passphrase, so the file is committable and useless without it. Deploy resolves ${VAR} references from it.

encryptionsalt: QHULMrEI25dNPdMnoC8y8A==
encryptioniters: 600000
config:
  EMAIL_FROM: noreply@example.com
  S3_ACCESS_KEY:
    secure: v1:...
  MISTRAL_MODEL: mistral-large-latest
CommandEffect
sklp config lslist keys, secrets shown as <secret>
sklp config get KEY [--show]print a value; --show decrypts a secret
sklp config set KEY VALUE [--secret]set a value; --secret stores it as secure:
sklp config rm KEYremove a key
sklp config import FILE [--secret]bulk-import a dotenv file; --secret encrypts every value
sklp config rotatere-seal every secret under a new passphrase and salt

Every verb takes --stack (default prod):

sklp config set --stack web EMAIL_FROM noreply@example.com
sklp config set --stack web --secret RESEND_API_KEY re_xxx
sklp config get --stack web RESEND_API_KEY --show
sklp config import --stack web --secret .env.prod

set rewrites the whole file in its canonical layout, so a large first diff is expected. Reference: sklp config.

The passphrase

sklp resolves the passphrase in this order:

OrderSourceSet with
1SKLP_SECRETS_PASSPHRASE env varexport (CI runners, relayed VMs)
2a lookup commandsklp config passphrase command '<cmd>'
3the OS vault (macOS keychain, libsecret)sklp config passphrase set

The command is stored in secrets.passphrase_command of the CLI config; it must print the passphrase on stdout, and {repo} in it is replaced by the repo key. It is run once when you set it, so a broken command fails now, not inside a pipeline. An empty argument clears it.

sklp config passphrase set
sklp config passphrase command 'pass show sklp/{repo}'
sklp config passphrase status    # source + whether it decrypts each file
sklp config passphrase unset

status names the source and the verdict per config file, never the value. A pipeline that asks for no secret runs without any passphrase.

Project secrets in core

sklp secret syncs a project's secrets between core and a local dotenv file (default .env.secret). Core never returns plaintext in a listing: only the name, the last four characters and whether deploy may read it.

sklp secret list --project <id>
sklp secret push .env.secret --dry-run
sklp secret push .env.secret             # deploy-readable by default
sklp secret pull                         # into .env.secret
sklp secret pull other.env --force       # another filename needs --force
sklp secret delete --project <id> OLD_KEY

Only deploy-readable secrets come back on pull. Reference: sklp secret.

Registry credentials

sklp registry login is the only way a push credential reaches sklp. sklp never reads ~/.docker/config.json: a credential this verb did not write does not exist, so a stale docker login cannot authenticate a push behind its back.

sklp registry login --provider scaleway --password-stdin < key.txt
sklp registry login ghcr.io --username me --credential-command 'pass show ghcr'
sklp registry ls       # host, username, secret source; never the secret
sklp registry logout ghcr.io

--provider (docker-hub, github, scaleway) pre-fills host and username. --credential-command stores a lookup command instead of the password. Pull credentials for nodes belong in the registries: block of a deploy.yaml, as ${VAR} references.

Rules

  • Never commit a secret value. Secrets go through config set --secret, secret push or a vault; config.yaml holds only secure: ciphertext.
  • Never write a literal password in a deploy.yaml. Use ${VAR}, resolved from the stack config.
  • Never print a secret to check it. Use config passphrase status or registry ls, which report sources and verdicts.
  • Rotate with config rotate, then share the new passphrase through the vault, not a chat.