Skip to main content
Guides

Secrets management

This guide covers pulling secrets from external backends into container environment variables. Moat supports 1Password, AWS Systems Manager Parameter Store (SSM), and host environment variable forwarding.

Secrets vs. credentials

Moat distinguishes between secrets and credentials:

SecretsCredentials
DeliveryEnvironment variablesNetwork-layer injection*
VisibilityVisible to all processes in containerNot visible in environment
Use caseSigning keys, database URLs, services without grant supportGitHub, Anthropic, OpenAI, AWS, SSH
Configurationsecrets: in moat.yamlgrants: in moat.yaml

*AWS uses credential_process instead of network-layer injection, making temporary credentials accessible in the container. See AWS credentials for details.

Use credentials (grants) when available. Use secrets for services that don’t have dedicated grant support.

1Password

Prerequisites

  1. Install the 1Password CLI:

    brew install 1password-cli
  2. Sign in:

    op signin

Configuration

Reference 1Password items using the op:// URL format:

secrets:
  JWT_SIGNING_KEY: op://Dev/Auth/jwt-signing-key
  DATABASE_URL: op://Production/Database/connection-string
  STRIPE_WEBHOOK_SECRET: op://Dev/Stripe/webhook-secret

Format: op://<vault>/<item>/<field>

How it works

When the run starts:

  1. Moat parses the secrets field
  2. For each op:// reference, Moat calls op read <reference>
  3. The resolved value is set as an environment variable in the container

Finding your item reference

Use the 1Password CLI to find item details:

# List vaults
op vault list

# List items in a vault
op item list --vault Dev

# Get item details
op item get "OpenAI" --vault Dev

# Copy the reference path from the output

Or use the 1Password desktop app: right-click a field and select “Copy Secret Reference”.

Example

name: my-agent

dependencies:
  - python@3.11

secrets:
  JWT_SIGNING_KEY: op://Dev/Auth/jwt-signing-key

command: ["python", "server.py"]
# server.py
import os
import jwt

signing_key = os.environ["JWT_SIGNING_KEY"]
# Use the signing key to create/verify JWTs...
token = jwt.encode({"user_id": 123}, signing_key, algorithm="HS256")
$ moat run

# JWT_SIGNING_KEY is available in the container

AWS SSM Parameter Store

Prerequisites

  1. Install the AWS CLI:

    brew install awscli
  2. Configure credentials:

    aws configure

    Or use environment variables:

    export AWS_ACCESS_KEY_ID="..."
    export AWS_SECRET_ACCESS_KEY="..."
    export AWS_REGION="us-east-1"

Configuration

Reference SSM parameters using the ssm:// URL format:

secrets:
  DATABASE_URL: ssm:///production/database/url
  API_KEY: ssm:///production/api-key

Format: ssm:///<parameter-path> or ssm://<region>/<parameter-path>

With explicit region

Specify the AWS region in the URL:

secrets:
  DATABASE_URL: ssm://us-west-2/production/database/url

Without a region, Moat uses the default from your AWS configuration.

How it works

When the run starts:

  1. Moat parses the secrets field
  2. For each ssm:// reference, Moat calls aws ssm get-parameter --name <path> --with-decryption
  3. The resolved value is set as an environment variable in the container

SecureString parameters

SSM supports encrypted parameters (SecureString). Moat automatically requests decryption with --with-decryption. Ensure your AWS credentials have permission to decrypt the parameter.

Required IAM permissions:

{
  "Effect": "Allow",
  "Action": [
    "ssm:GetParameter"
  ],
  "Resource": "arn:aws:ssm:*:*:parameter/production/*"
}

For encrypted parameters, also add:

{
  "Effect": "Allow",
  "Action": [
    "kms:Decrypt"
  ],
  "Resource": "arn:aws:kms:*:*:key/<key-id>"
}

Example

name: my-agent

dependencies:
  - node@22

secrets:
  DATABASE_URL: ssm:///production/database/url
  REDIS_URL: ssm:///production/redis/url

command: ["node", "server.js"]
$ moat run

# DATABASE_URL and REDIS_URL are available in the container

Host environment variables

Forward environment variables from your host machine into the container. This is convenient for local development and low-risk configuration, but less secure than external secret backends.

Configuration

Reference host variables using the env:// URL format:

secrets:
  MY_API_KEY: env://MY_API_KEY
  DATABASE_URL: env://LOCAL_DB_URL

Format: env://<VARIABLE_NAME>

The variable name after env:// is the host environment variable to read. The key on the left is the name it gets inside the container — these can differ.

How it works

When the run starts:

  1. Moat parses the secrets field
  2. For each env:// reference, Moat reads the variable from the host environment
  3. If the variable is not set, the run fails with an error
  4. The resolved value is set as an environment variable in the container

Example

name: my-agent

secrets:
  OPENAI_API_KEY: env://OPENAI_API_KEY
  CUSTOM_ENDPOINT: env://MY_ENDPOINT

command: ["python", "app.py"]
$ export OPENAI_API_KEY="sk-..."
$ export MY_ENDPOINT="https://api.example.com"
$ moat run

# OPENAI_API_KEY and CUSTOM_ENDPOINT are available in the container

When to use

Use env:// for:

  • Local development where secrets are already in your shell environment
  • CI/CD pipelines that inject secrets as environment variables
  • Low-risk configuration values that don’t warrant a secret manager

For production secrets, prefer 1Password (op://) or AWS SSM (ssm://). For credentials with dedicated grant support (GitHub, Anthropic, OpenAI, AWS), use grants: instead.

Combining multiple backends

Use secrets from different backends in the same configuration:

secrets:
  # From 1Password
  JWT_SIGNING_KEY: op://Dev/Auth/jwt-signing-key

  # From AWS SSM
  DATABASE_URL: ssm:///production/database/url

  # From host environment
  CUSTOM_API_KEY: env://CUSTOM_API_KEY

All secret values must use a URI scheme (e.g., op://, ssm://, or env://). Plain values like LOG_LEVEL: debug are not supported — use the env: field for non-secret environment variables instead.

For services with dedicated grants (GitHub, Anthropic, OpenAI, AWS), use grants: instead of secrets:. Grants provide better security by injecting credentials at the network layer.

Security considerations

Secrets are environment variables. They are visible to:

  • All processes in the container
  • Code that reads /proc/*/environ
  • Logging that dumps environment variables

For sensitive credentials like OAuth tokens, use grants instead of secrets. Grants inject credentials at the network layer where they’re not visible in the environment.

Secrets are resolved on your host machine. The 1Password CLI and AWS CLI run on your host, not in the container. Your host must have access to the secret backends.

Secrets are logged in the audit trail. Secret resolution events (which secrets were resolved, not their values) are recorded in the audit log.

Troubleshooting

”op: command not found”

Install the 1Password CLI:

brew install 1password-cli

“You are not currently signed in”

Sign in to 1Password:

op signin

“aws: command not found”

Install the AWS CLI:

brew install awscli

“Unable to locate credentials”

Configure AWS credentials:

aws configure

Or set environment variables:

export AWS_ACCESS_KEY_ID="..."
export AWS_SECRET_ACCESS_KEY="..."

“ParameterNotFound”

Verify the parameter exists:

aws ssm get-parameter --name /production/database/url

Check the path format—SSM paths start with /.

”AccessDeniedException”

Your AWS credentials lack permission to read the parameter. Check IAM policies.