Deploy A MongoDB Replica Set with Cycle MCP.

A replica set is more than three containers. The members have to find each other, share a secret, agree on a primary, and replicate your writes. This page walks through asking the LLM (I'll use Claude for this guide) to build one on Cycle with the Cycle MCP server, then prove it works.

The concept

A MongoDB replica set on Cycle is three stateful containers, each running a single instance, in one environment. Generally, this is deployed together as a stack. Each member gets its own volume and its own hostname (mongo-0, mongo-1, mongo-2). The members reach each other by hostname over the environment's private network, which in most cases is IPv6-only. So the MCP will naturally run mongod with --ipv6 as well as --bind_ip_all.

Authentication adds two secrets. The keyfile is a shared secret the members use to authenticate each other. Setting it also turns on access control, so every client has to log in too. The admin password is for you and your applications. The MCP will generally understand that neither belongs in the stack, because stacks store command arguments and environment variables in plain text (unless you're using stack variables). In testing the MCP was able to disseminate that each variable would be best as a scoped variable marked secret, which Cycle writes into each member as a file when it starts.

The order follows from that. The containers are created stopped. The secrets are created next, then the containers start, so every member boots with its keyfile already in place. Only then can the replica set be initiated from inside one member.

If using the connector Claude or Codex will need the write and exec scopes for this: write to create resources, and exec to run mongosh inside a member. If the connector is missing either, see this article on managing permissions.

Do it

This example builds replica set rs0 in a new environment acme-db on the production cluster.

  1. Ask Claude in one prompt. Naming the environment and asking for confirmations up front means Claude doesn't have to choose either for you:
    "Create a new environment called acme-db in the production cluster, then deploy a MongoDB replica set to it and prove that it's up and working. Before each change, tell me exactly what you're about to do and wait for my OK."
  2. Claude checks the ground and gets the MongoDB hint. It reads the cluster with list_servers and checks the name is free with list_environments. It also calls deploy_application with application: mongodb and no containers. That returns Cycle's recommended MongoDB configuration and changes nothing: mongo:7, mongod --replSet rs0 --bind_ip_all --ipv6, a 10G volume at /data/db, and one stateful container per member.
  3. Confirm the environment and choose authentication. Claude asks you to approve the environment, and asks how the replica set should authenticate.
    Choosing: without authentication, anything on the environment's private network can read and write the database. That's acceptable for a throwaway test. With a keyfile and an admin user, members authenticate each other and every client needs credentials. This example uses a keyfile and an admin user, which adds a step to create the two secrets and a command to create the admin user.
    After you approve, create_environment creates acme-db with legacy networking off and starts it.
  4. Confirm the stack. Claude previews the deploy, then describes it:
    "I'm about to create stack acme-mongo and deploy it into acme-db. It holds 3 containers, mongo-0, mongo-1 and mongo-2, each with image mongo:7, command mongod --replSet rs0 --bind_ip_all --ipv6 --keyFile /etc/mongo/keyfile, 1 stateful instance, a 10G volume at /data/db, port 27017:27017, and public network disabled. The containers are created stopped. OK to deploy?"
    After you confirm, deploy_application builds the stack and creates the three containers, all stopped.
  5. Confirm the secrets and the start. Claude generates a random keyfile and a random admin password, previews both scoped variables, and asks two questions in one prompt. The first is:
    "I'm about to create 2 secret scoped variables in acme-db, both reaching mongo-0, mongo-1 and mongo-2: mongo-keyfile, delivered as the file /etc/mongo/keyfile, and mongo-admin-password, delivered as the file /etc/mongo/admin-password. Both files have mode 0400 and are owned by uid and gid 999, the mongodb user. OK to create both?"
    The second asks whether to start all three containers once the secrets exist.
    After you confirm, manage_scoped_variable creates both variables and cycle_control_container starts each member. Claude then waits with get_logs for mongod to log Waiting for connections, and checks with list_containers that all three are running.
    Note for real usage: when Claude generates the secrets, their values pass through the conversation. For anything beyond a test, create the two scoped variables yourself in the portal, with the same file paths, mode 0400 and owner uid and gid 999, and give Claude only their identifiers.
  6. Confirm the replica set setup. Claude previews two commands for run_instance_command on mongo-0, and asks before running them. The first initiates the replica set and waits for mongo-0 to become primary, the second creates the admin user with the root role, reading the password from the file inside the container:

    Both should returne ok: 1, and mongo-0 reported isWritablePrimary=true.
  7. Confirm the proof. Claude previews two more commands and asks before running them, as it does for every command it runs inside a container. One of them writes a test document, the only data change in the proof. In this example they returned:

    Check

    Where

    Result

    Request without credentials

    mongo-0

    Rejected with Unauthorized

    rs.status() over the replica-set URI mongo-0,mongo-1,mongo-2/?replicaSet=rs0

    mongo-0

    mongo-0 PRIMARY, mongo-1 and mongo-2 SECONDARY, all health 1

    Insert {_id:"proof-1"} into mcp_proof.checks with write concern majority

    mongo-0

    Acknowledged

    Read proof-1 back, connected directly to the member

    mongo-2

    secondary=true, primary=mongo-0:27017, document returned

What just happened

Each check in the proof tests a different layer. The rejected request shows that authentication is enforced, not just configured. rs.status() ran on the primary, and it reports health 1 for a member only while the primary's heartbeats to that member succeed. So all three being healthy means the primary reaches both secondaries by hostname over IPv6. A write with write concern majority is acknowledged only after at least two of the three members have it. Reading the same document on a secondary, over a direct connection to that member, shows it replicated rather than being read from the primary, which also means that secondary reaches the primary.

The keyfile is checked twice. mongod refuses to start when its keyfile is missing or readable by other users, so mongo-0 logging Waiting for connections shows it read its keyfile. Members also authenticate to each other with the keyfile, so mongo-1 and mongo-2 becoming healthy secondaries shows their keyfiles matched. Listing /etc/mongo inside mongo-0 showed both files owned by mongodb with mode 0400, as the scoped variables specified.

Nobody needed a password to set the replica set up. Until the first user exists, MongoDB's localhost exception lets a connection from inside the server run a small set of setup commands, including rs.initiate and creating that first user. Both ran through it, from inside mongo-0 with run_instance_command. Creating the admin user closes that door, which is why the unauthenticated request in the proof was rejected.

The password never appears in the command text Claude shows you. Every command reads it from /etc/mongo/admin-password inside the container, so the text you approve contains only the file path. That isn't the same as keeping it off the command line. In the proof commands, the shell expands -p "$PW" into mongosh's arguments, so the password is briefly visible in that member's process list. createUser reads it inside JavaScript instead, which avoids that. The values did pass through the conversation, when Claude generated them and sent them to manage_scoped_variable. That's why step 5 suggests creating the variables yourself for real data.

Every run_instance_command call was previewed first. The preview echoes the exact command and target without connecting, so you approve exactly what runs. That matters more here than elsewhere, because exec gives Claude a shell inside your database.


Cookies

Cookies Preferences

We run basic, anonymous analytics by default to measure site traffic. By clicking "Accept," you allow additional cookies for advanced app improvements and tailored advertising. Choose what you share by clicking "Customize."