CI/CD Integration
1ctl is designed to run in automated pipelines. The key difference from interactive use: pass your API token via the SATUSKY_API_KEY environment variable instead of running 1ctl auth login. No state files, no interactive prompts.
You will also need to set SATUSKY_API_URL — SATUSKY_API_KEY alone is not sufficient. Both environment variables must be present for the CLI to reach the API.
Creating a CI Token
Section titled “Creating a CI Token”Create a dedicated token for your pipeline. A dedicated token lets you revoke CI access independently from your personal account — if a pipeline is compromised, you disable that one token without affecting your own credentials.
1ctl token create --name "github-actions" --expires 90The --expires value is in days. Use 0 for no expiry, though setting an expiry and rotating regularly is good practice.
The output looks like this:
✅ API token created successfullyID: 24ae4338-9829-463d-a31b-9627f41c6434Name: github-actions
❗️ IMPORTANT: Save this token now. You won't be able to see it again!Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Expires: 2026-07-27The token value is shown exactly once. Copy it immediately and save it as a secret in your CI system. If you lose it, delete the token and create a new one.
Managing CI Tokens
Section titled “Managing CI Tokens”Listing tokens
Section titled “Listing tokens”1ctl token listOutput shows one record per token:
API Tokens──────────ID: 24ae4338-9829-463d-a31b-9627f41c6434Name: github-actionsStatus: EnabledLast Used: 2 hours agoExpires: 2026-07-27Created: just now---For scripting, use JSON output with the global -o json flag. Note that -o json must come before the subcommand:
# Correct: global flag before subcommand1ctl -o json token list
# Wrong: flag after subcommand is not recognized1ctl token list -o jsonJSON field names returned:
[ { "token_id": "24ae4338-9829-463d-a31b-9627f41c6434", "name": "github-actions", "description": "", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "expires_at": "2026-07-27T09:04:35.625037+08:00", "is_active": true, "created_at": "2026-04-28T09:04:35.666822+08:00" }]Use token_id (not id) when scripting against this output.
Disabling and enabling tokens
Section titled “Disabling and enabling tokens”Temporarily block a token without deleting it — useful when rotating credentials:
1ctl token disable <token-id># Output: ✅ Token disabled successfully
1ctl token enable <token-id># Output: ✅ Token enabled successfullyDeleting tokens
Section titled “Deleting tokens”# --yes must come before the token ID1ctl token delete --yes <token-id># Output: ✅ Token deleted successfullyWithout --yes, the CLI prompts for confirmation. In non-interactive scripts, always include --yes before the token ID.
Environment Variables in CI
Section titled “Environment Variables in CI”Two variables are required in every CI job that runs 1ctl:
| Variable | Purpose |
|---|---|
SATUSKY_API_KEY | Your API token value (the long eyJ... string) |
SATUSKY_API_URL | The API endpoint. Production: https://api.satusky.com/v1/cli |
SATUSKY_API_KEY overrides any stored credentials from 1ctl auth login. If both a stored session and SATUSKY_API_KEY are present, the environment variable takes precedence.
SATUSKY_API_URL must always be set explicitly in CI — the CLI does not fall back to a default URL.
GitHub Actions
Section titled “GitHub Actions”These YAML workflows run on GitHub Actions runners. Adapt the
SATUSKY_API_KEYandSATUSKY_API_URLenvironment variables to your specific CI system’s secrets mechanism. The1ctlcommands themselves are identical regardless of CI platform.
Basic deployment workflow
Section titled “Basic deployment workflow”Add two secrets to your repository: Settings → Secrets and variables → Actions → New repository secret.
SATUSKY_API_TOKEN— the token value from1ctl token createSATUSKY_API_URL—https://api.satusky.com/v1/cli
Note: The install script at
https://install.satusky.comis the production installer. It is not available in local development environments. In CI it downloads the latest1ctlrelease.
name: Deploy
on: push: branches: [main]
jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Install 1ctl run: curl -fsSL https://install.satusky.com | sh
- name: Deploy env: SATUSKY_API_KEY: ${{ secrets.SATUSKY_API_TOKEN }} SATUSKY_API_URL: ${{ secrets.SATUSKY_API_URL }} run: 1ctl deploy --waitWhy --wait matters in CI: without it, 1ctl deploy exits as soon as the deploy request is accepted by the API — before pods have actually started. The pipeline step goes green while your new version may still be failing to boot. --wait blocks until pods reach Running state (default timeout: 5 minutes), so a failed rollout causes the CI step to fail as expected.
Multi-environment workflow
Section titled “Multi-environment workflow”Deploy to staging on every pull request, production only on main. This requires separate config files (satusky.staging.toml, satusky.production.toml) in your repository, and separate secrets for each environment’s API key.
Infrastructure note: multi-environment deploys require separate Satusky namespaces or projects configured per environment. This YAML pattern shows the CI structure; the namespace and resource allocation differences live in your config files.
name: Deploy
on: push: branches: [main, staging] pull_request: branches: [main]
jobs: deploy-staging: if: github.event_name == 'pull_request' || github.ref == 'refs/heads/staging' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install 1ctl run: curl -fsSL https://install.satusky.com | sh - name: Deploy to staging env: SATUSKY_API_KEY: ${{ secrets.SATUSKY_API_TOKEN_STAGING }} SATUSKY_API_URL: ${{ secrets.SATUSKY_API_URL }} run: 1ctl deploy --config satusky.staging.toml --wait
deploy-production: if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install 1ctl run: curl -fsSL https://install.satusky.com | sh - name: Deploy to production env: SATUSKY_API_KEY: ${{ secrets.SATUSKY_API_TOKEN_PRODUCTION }} SATUSKY_API_URL: ${{ secrets.SATUSKY_API_URL }} run: 1ctl deploy --config satusky.production.toml --waitThe --config flag accepts a filename or a short name. --config staging resolves to satusky.staging.toml in the working directory.
Deploy with rollback on failure
Section titled “Deploy with rollback on failure”This pattern captures the deployment ID after a successful deploy, then rolls back to the previous version if any subsequent step fails:
- name: Deploy id: deploy env: SATUSKY_API_KEY: ${{ secrets.SATUSKY_API_TOKEN }} SATUSKY_API_URL: ${{ secrets.SATUSKY_API_URL }} run: | 1ctl deploy --wait # Capture the deployment ID of the most recent deploy for rollback use DEP_ID=$(1ctl -o json deploy list | jq -r '.[0].deployment_id') echo "deployment_id=$DEP_ID" >> $GITHUB_OUTPUT
- name: Rollback on failure if: failure() && steps.deploy.outputs.deployment_id != '' env: SATUSKY_API_KEY: ${{ secrets.SATUSKY_API_TOKEN }} SATUSKY_API_URL: ${{ secrets.SATUSKY_API_URL }} run: | 1ctl deploy rollback \ --deployment-id ${{ steps.deploy.outputs.deployment_id }} \ --yesWhat this does: deploy list returns deployments sorted most-recent-first, so .[0].deployment_id is the deployment just created. If a post-deploy health check or smoke test step fails, the Rollback on failure step runs and reverts to the previous release automatically.
Why --yes on rollback: rollback without --yes prompts for confirmation, which hangs indefinitely in a non-interactive CI runner.
--version is optional: omitting it rolls back to the immediately preceding release, which is what you want in most CI failure scenarios. You can specify --version 3 to target a specific release number from 1ctl deploy releases.
GitLab CI
Section titled “GitLab CI”This YAML runs on GitLab CI runners. Set
SATUSKY_API_TOKENandSATUSKY_API_URLin Settings → CI/CD → Variables.
stages: - deploy
deploy: stage: deploy image: alpine:latest script: - apk add --no-cache curl - curl -fsSL https://install.satusky.com | sh - 1ctl deploy --wait variables: SATUSKY_API_KEY: $SATUSKY_API_TOKEN SATUSKY_API_URL: $SATUSKY_API_URL only: - mainInjecting Environment Variables at Deploy Time
Section titled “Injecting Environment Variables at Deploy Time”You can pass runtime environment variables directly in the deploy command. This is useful when secrets differ per pipeline run or per branch:
1ctl deploy \ --env DATABASE_URL=$DATABASE_URL \ --env REDIS_URL=$REDIS_URL \ --waitFor long-lived secrets (database passwords, API keys that don’t change per-build), pre-configure them with 1ctl secret create. Secrets persist across deployments — you don’t need to pass them on every deploy invocation.
Using -o json in Scripts
Section titled “Using -o json in Scripts”Use the global -o json flag for machine-readable output. The flag must appear before the subcommand:
# Get the deployment ID of your backend-api appDEPLOYMENT_ID=$(1ctl -o json deploy list | jq -r '.[] | select(.app_label=="backend-api") | .deployment_id')
# Get the most recent deployment ID regardless of appDEPLOYMENT_ID=$(1ctl -o json deploy list | jq -r '.[0].deployment_id')
# Check deployment statusSTATUS=$(1ctl -o json deploy get --config satusky.toml | jq -r '.status')if [ "$STATUS" != "completed" ]; then echo "Deploy is not healthy: $STATUS" exit 1fiCorrect JSON field names (common mistakes highlighted):
| What you want | Correct field | Wrong field |
|---|---|---|
| Deployment identifier | .deployment_id | .id |
| App name | .app_label | .name |
| Deployment status | .status | — |
| Token identifier | .token_id | .id |
| Token active state | .is_active | .status |
Viewing Release History
Section titled “Viewing Release History”1ctl deploy releases --config satusky.tomlOutput:
VERSION IMAGE STATUS DEPLOYED─────── ──────────── ─────────── ────────────6 nginx:alpine active 23 hours ago5 nginx:alpine superseded 23 hours ago4 nginx:alpine superseded 23 hours ago1 nginx:alpine rolled_back 23 hours agoUse the VERSION number with 1ctl deploy rollback --version <n> to target a specific release.
Best Practices
Section titled “Best Practices”Use dedicated CI tokens — create one token per pipeline (e.g. github-actions, gitlab-ci). This scopes the blast radius if a token is leaked: you revoke one token without disrupting other pipelines or your personal access.
Set a token expiry — --expires 90 rotates credentials quarterly. Rotate before expiry with 1ctl token create, update the secret in your CI system, then delete the old token with 1ctl token delete --yes <old-id>.
Always use --wait — without it your pipeline marks the deploy step green before pods have actually started. A bad deploy that crashes on startup will not be caught.
Use --yes on non-interactive commands — token delete, deploy rollback, and deploy destroy all prompt for confirmation. Always include --yes (before the positional argument) in scripts.
Separate configs per environment — keep satusky.staging.toml and satusky.production.toml with different resource allocations, app names, and domain settings. This prevents a staging pipeline from accidentally deploying to production.
Pin the 1ctl version — the install script installs the latest release. To avoid unexpected behavior from version upgrades, pin a specific version tag in your install step once your pipeline is stable.