Skip to content

Automated OpenCode and MCP Credentials Provisioning via SOPS

Previously, OpenCode server and remote Model Context Protocol (MCP) integrations (Exa, Context7) required manual credential configuration after sandbox initialization, and OPENCODE_GO_API_KEY set via environment variables did not register in OpenCode WebUI without account integration. We decided to automate credential provisioning through SOPS: secrets/opencode.env stores encrypted API keys (OPENCODE_GO_API_KEY, EXA_API_KEY, CONTEXT7_API_KEY), scripts/setup.sh decrypts them via a new --opencode flag to ~/.config/opencode/opencode.env and exports them to the environment, shared/opencode.json configures MCP servers using {env:...} header substitution and configures native OpenCode Go provider environment mapping via provider.opencode-go.env, and sandbox startup scripts export OPENCODE_API_KEY before launching opencode serve.

Considered Options

  • Manual interactive setup via OpenCode CLI/WebUI: Require users to run opencode auth login or enter keys in the WebUI manually on every ephemeral sandbox creation. Rejected because it disrupts automated workflows and opencode auth login refuses non-interactive TTY input.
  • Direct SQLite database injection prior to server startup: Write credentials directly into /root/.local/share/opencode/opencode.db using a custom Python script. Rejected because it couples container bootstrap to internal SQLite schemas and credential serialization formats that can drift across OpenCode releases.
  • Process-scoped environment without /etc/environment or ~/.bashrc export: Source opencode.env strictly for the opencode serve command. Rejected because subshells spawned by OpenCode and developer terminal/SSH sessions need ambient access to EXA_API_KEY and CONTEXT7_API_KEY for CLI tools and scripts.
  • Post-startup REST API registration loop: Run opencode api POST /api/integration/opencode-go/connect/key inside startup.sh once the server port responds. Rejected because OpenCode's internal provider catalog boots 1–2 seconds after the HTTP port opens, causing transient 404 IntegrationNotFoundError race conditions on container launch.
  • Automated SOPS decryption with native provider environment mapping: Extend scripts/setup.sh with an orthogonal --opencode flag, decrypt secrets/opencode.env during SOPS bootstrap in startup.sh, source the decrypted credentials and export OPENCODE_API_KEY in startup.sh, and declare "provider": { "opencode-go": { "env": ["OPENCODE_GO_API_KEY"] } } in shared/opencode.json. Accepted because it leverages OpenCode's native provider environment discovery, eliminates startup race conditions and background API polling, keeps secrets securely managed at rest via SOPS without hardcoding keys in tracked files, and activates all OpenCode Go models synchronously on server boot.

Consequences

  • scripts/setup.sh accepts --opencode (decrypts secrets/opencode.env to ~/.config/opencode/opencode.env mode 0600) and exports key-value pairs into /etc/environment (if writable) and ~/.bashrc.
  • shared/opencode.json configures remote MCP server exa with header "x-api-key": "{env:EXA_API_KEY}", context7 with "oauth": false and header "CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}", and native provider mapping "opencode-go": { "env": ["OPENCODE_GO_API_KEY"] }.
  • standard_sandbox/startup.sh and vm_sandbox/startup.sh include --opencode when ENABLE_OPENCODE=1, trigger SOPS decryption, export decrypted credentials including OPENCODE_API_KEY before launching opencode serve, and rely on OpenCode's native environment discovery without background REST API calls.
  • If ENABLE_OPENCODE=1 is passed to sandbox runners but AGE_PRIVATE_KEY is missing from the secret collection, sandbox creation fails fast with an explicit error.