Platform

Platform overviewArchitectureWorkflow orchestrationGitOps configurationGovernance and AAAAI and MCPKnowledge and contextRuntime and executionEvidence and monitoring

Use cases

All use casesProduction incidentRelease preparationHotfix to productionSecurity scan triage
Why NopsAIIntegrationsSecurity

Resources

All resourcesAI agent governanceMCP governanceMCP securitySelf-hosted platforms
PricingGitHub

Company

How a run worksAboutContactBook a demo

Add a variable and a secret

Store a scoped variable and an encrypted secret, then consume both from the pipeline you just built.

TutorialNew userAutomation authorSecurity

What you will do

  • A scope is the namespace a run resolves variables and secrets in, and it is chosen when the run starts. Variables and scopes has the resolution rules; Credentials and secrets store covers the platform credential registry.
  • PUT /v1/variables/{name}?scope=<scope> and PUT /v1/secrets/{name}?scope=<scope> both take {"value": "..."}.
  • Listing secrets returns names and metadata only. Reading a value is a separate route guarded by its own action, secret.read_value.
  • A pipeline declares what it needs: variables at the top level, and secrets on the step that consumes them.
  • A bare name resolves in the run scope; scope/path:NAME resolves in another scope and is still injected under the bare name.
  • A declared variable that cannot be resolved fails the run before execution instead of starting with a missing value.

Before you start

Pipeline
The pipeline from the previous page, saved and runnable
Scope
The scope you ran it in, for example platform/production
Token
A token allowed to write variables and secrets in that scope

Steps

  1. 01

    Store a variable

    Variables are plain configuration. They are readable back through the API, which makes them the wrong place for credentials.

    Create a scoped variablebash
    curl -sX PUT "http://localhost:8080/v1/variables/RELEASE_CHANNEL?scope=platform/production" \
      -H "Authorization: Bearer $NOPSAI_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{"value":"stable"}'
    Verify
    • curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "http://localhost:8080/v1/variables?scope=platform/production" | jq
  2. 02

    Store a secret

    Secrets are encrypted with the master key before storage. The value can be read back later, but only by a caller granted secret.read_value on that secret.

    Important
    • Losing NOPSAI_MASTER_KEY makes every stored secret unreadable. It belongs in the same place as your other break-glass material.
    Create a scoped secretbash
    RELEASE_TOKEN=$(openssl rand -hex 12)
    curl -sX PUT "http://localhost:8080/v1/secrets/RELEASE_TOKEN?scope=platform/production" \
      -H "Authorization: Bearer $NOPSAI_TOKEN" \
      -H "Content-Type: application/json" \
      -d "{\"value\":\"$RELEASE_TOKEN\"}"
  3. 03

    Declare them in the pipeline

    The pipeline states what it requires. The variable is declared once at the top level; the secret is declared on the step that needs it, so it is not injected into steps that do not.

    Extend first-pipeline.yamlyaml
    name: first-pipeline
    container_image: alpine:3.20
    variables:
      - RELEASE_CHANNEL
    steps:
      - name: prepare
        script: |
          echo "1.0.$(date +%s)" > /nopsai/outputs/BUILD_TAG
          echo "channel is $RELEASE_CHANNEL"
        outputs:
          - name: BUILD_TAG
    
      - name: publish
        depends_on: [prepare]
        secrets:
          - RELEASE_TOKEN
        variables:
          BUILD_TAG: $steps.prepare.outputs.BUILD_TAG
        script: |
          echo "publishing $BUILD_TAG to $RELEASE_CHANNEL"
          echo "token length is ${#RELEASE_TOKEN}"
    Result

    The step can use the secret, but printing it would show as a masked value in the logs.

  4. 04

    Run it and check redaction

    Run in the same scope, then read the logs and confirm the secret value never appears in clear text.

    Run and inspectbash
    curl -sX POST http://localhost:8080/v1/run/first-pipeline \
      -H "Authorization: Bearer $NOPSAI_TOKEN" \
      -H "Accept: application/json" \
      -H "Content-Type: application/json" \
      -d '{"scope":"platform/production"}' | jq -r .run_id
    Verify
    • The logs show the channel and the token length, never the token itself.

How it works

Names must match ^[A-Za-z0-9_.-]+$, and the same runtime name may not resolve from two different scopes in one place — that is a conflict, not a precedence rule.

Cross-scope use is explicit: platform/shared:ARTIFACT_BUCKET resolves in platform/shared and is injected as ARTIFACT_BUCKET. It never happens implicitly.

To keep a secret in a configuration repository instead of the database, encrypt it first through POST /v1/secrets/encrypt; the envelope is safe to commit and resolves at run time.

Step variables apply to every task in a step; task variables are applied afterwards and win.

Scope is not team path. Scope decides which values resolve; team path decides who owns the resource and who is notified.

Limits

Current behavior
  • A secret value is recoverable by anyone granted secret.read_value on it. Grant that action deliberately: it is the difference between storing a credential and publishing it.

Implementation evidence

  • services/nopsai/secrets_variables_handlers.go

    Scope query parameter, request body, and encryption on write.

  • pkg/models/runtime_refs.go

    Scoped reference parsing and name validation.