Run Apps Without Static Secrets
Deploy services that authenticate themselves without API keys or secrets in environment variables.
Ask Claude to deploy your application to Fly.io. The deployed service identifies itself with a runtime OIDC token — no API keys to store or rotate.
Prerequisites
Section titled “Prerequisites”-
Application running locally from Access APIs on Behalf of Users
-
Fly.io account with
flyctlinstalled (sign up if you don’t have one) -
Your
keycard run -- claudesession should still be running
Walkthrough
Section titled “Walkthrough”-
Ask Claude to deploy
Inside your Keycard session, send a prompt like:
Deploy my MCP server to Fly.io.
-
Authenticate to Fly.io
There are two ways to give Claude access to Fly.io for deployment. Pick the one that fits your workflow:
Fly.io doesn’t support federated token exchange, so you store a Fly.io deploy token in Keycard as a vaulted static credential. Keycard brokers access without persisting credentials on your machine.
Claude handles the setup: it creates the vault resource, checks your access policy, and links you to the Keycard Console credentials page. Create a deploy token at fly.io/tokens scoped to your Fly.io organization, then open the link Claude gives you and paste the token under Credentials.
The token is encrypted at rest. Claude never sees the raw value; it only receives it through token exchange at deployment time. When Claude needs the token, Keycard shows a consent screen so you can approve before it proceeds:

Run
flyctl auth loginin your terminal. This authenticates your local user to Fly.io, and Claude can useflyctlcommands on your behalf within the Keycard session.Terminal window flyctl auth loginUse a Keycard access policy to restrict which
flyctlcommands the agent can run. For example, you can allowflyctl deploybut require ITL approval forflyctl destroy. -
Claude configures and deploys
Claude generates the Fly.io config, wires up Keycard trust for workload identity, registers the production resource, and deploys the container.
Here’s how the scaffolded code handles credential discovery and workload identity:
// credentials.ts - auto-detects the runtime and picks the right credentialexport function discoverApplicationCredential(options: DiscoverOptions,): ApplicationCredential {const clientId = process.env.KEYCARD_CLIENT_ID;const clientSecret = process.env.KEYCARD_CLIENT_SECRET;if (clientId && clientSecret) {return new ClientSecret(clientId, clientSecret); // local dev via keycard run}if (process.env.FLY_APP_NAME) {return new FlyWorkloadIdentity({ audience: options.zoneUrl }); // production}// ... EKS, web identity, and other runtimes}// credentials.ts - Fly.io OIDC token fetch via the runtime metadata endpointclass FlyWorkloadIdentity implements ApplicationCredential {async prepareTokenExchangeRequest(subjectToken: string, resource: string) {const oidcToken = await this.#getToken(); // fetched from /.fly/apireturn {subjectToken,resource,clientAssertionType: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",clientAssertion: oidcToken,};}} -
Test the production deployment
Your MCP server is live at
https://<project-name>.fly.dev. Ask Claude to call one of your server’s tools. The same tools that worked locally now run through the deployed server, but the server authenticates with workload identity instead ofkeycard run. -
Verify in Keycard Console
Open Keycard Console > Audit Log. You should see entries for the tool call flowing through your production server:
users:authenticateYou logged in successfully users:authorizeYour access was authorized credentials:issueAccess token was issued Same event types as local development. Authorization and audit don’t change based on where the server runs.
Patterns
Section titled “Patterns”Your service is live with no stored secrets. Here’s what Claude set up and how to apply it to other runtimes.
Workload Identity
Section titled “Workload Identity”Locally, keycard run injects credentials at startup. In production there’s no human present, so the deployed service needs its own identity to authenticate with Keycard and request resources on behalf of callers.
Claude registered a Fly.io OIDC provider in your Keycard instance. When your server starts on Fly.io, it gets an OIDC token from the runtime. Keycard verifies that token against the registered provider and issues credentials — no stored secrets involved.
Keycard
ephemeral credentialThe application credential Claude configured is what ties this together. It specifies which Fly.io workloads can act as your application, scoped to your organization and app name. See Applications for the identity and credential model.
Vaulted Static Credentials
Section titled “Vaulted Static Credentials”Some upstream resources only offer static API keys or long-lived tokens — no ephemeral token exchange. For those, Keycard stores the credential as a vaulted static credential and brokers access through token exchange.
Only use vaulted credentials when native token exchange isn’t available. They have two drawbacks:
- You need to manually rotate the credential in Keycard Console when it expires or gets compromised.
- Every application that requests this resource receives the same static credential, so you can’t scope access per-caller or per-request.
If a resource supports OIDC or OAuth token exchange, use that instead. Ephemeral tokens expire on their own and don’t need rotation.
Production Resource Configuration
Section titled “Production Resource Configuration”Locally, your MCP server’s resource identifier points to localhost. In production, Keycard needs the server’s public URL so callers can find it.
Claude registered a second resource (https://<project-name>.fly.dev/mcp) alongside the existing localhost resource. Both belong to the same application. MCP clients use this identifier to locate your server, and callers authenticate through Keycard, which sends them to the right endpoint.
Both resources are visible in Keycard Console under your application’s resource configuration.
Troubleshooting
Section titled “Troubleshooting”Claude is still calling localhost instead of the deployed URL
After deployment, Claude’s MCP client config may still point to http://localhost:3000/mcp. Ask Claude to update the MCP config to use the new production address (https://<project-name>.fly.dev/mcp), then restart the MCP connection.
Token exchange fails with “provider not found”
Keycard doesn’t recognize the Fly.io OIDC issuer yet. Ask Claude to register the Fly.io OIDC provider in your Keycard zone, or check Keycard Console > Providers to verify it’s listed.
Deployment succeeds but the server returns 401
The application credential may not match the deployed workload. Verify in Keycard Console > Applications that the credential’s Fly.io app name and organization match your deployment. A mismatch means the runtime OIDC token won’t pass validation.
What’s next
Section titled “What’s next”Your service is running in production with no managed credentials. The next guide covers giving an agent its own access to third-party APIs, with no human in the loop.
- Grant Agent Access to APIs: build an agent that authenticates to external services like Snowflake