Run QM, the open source multiplayer agent harness, on your own Porter cluster with agents in Porter Sandboxes, a Postgres database, and an optional S3 bucket
QM is an open source, multiplayer agent harness. Your team works with agents in a shared workspace where context and files carry across your organization. Porter runs QM as an add-on on your cluster, and every agent runs in a Porter Sandbox on the same cluster.To see what teams build with it, read QM use cases.
QM is currently only available on AWS. If you don’t see QM in the add-on list, reach out via the support widget on the dashboard.
Click Connect existing datastore to fill in the URL from a Porter datastore, or paste a postgres://user:password@host:5432/qm URL.
File storage
Pick the bucket for uploads, snapshots, and Files data.
Without a bucket, QM keeps files in temporary storage, and they’re lost whenever QM restarts or updates. Attach a bucket for anything beyond a trial.
Create datastore and Create bucket leave the form and discard your changes. Create them before you start.
3
Choose how people sign in
Password is the quickest way to start. Porter fills in your email and generates a password. You can switch to another sign-in method at any time.
Copy the password before you click Deploy. Porter stores only a hash of it, so it can’t be shown again.
Once QM is deployed, open the URL in the add-on’s header and sign in with this account. The URL is your custom domain if you set one, otherwise the Porter domain.QM doesn’t need a model provider to install. After your first sign-in, set the base model in the QM admin console. The add-on’s Integrations tab also takes fallback API keys for Anthropic, OpenAI, and OpenRouter, plus Slack bot credentials.
4
Add administrators and deploy
Administrators can manage QM and can always sign in. Your email is added for you. Invite everyone else from the QM admin console after first boot.Click Deploy.
The Porter MCP server lets an AI agent such as Claude Code or Cursor install and manage QM for you.
1
Install the Porter MCP server and skills
curl -fsSL https://agents.porter.run | sh
Restart your agent afterward. The install adds the porter-qm skill, which takes a cluster to a signed-in QM install.
2
Ask your agent to set up QM
For example:
Set up QM on my Porter cluster. Use this Postgres database: postgres://user:password@host:5432/qm
The agent asks for anything it still needs in one round of questions: the cluster, administrators, and how people sign in. It then enables sandboxes if the cluster doesn’t have them, installs QM, waits for it to be ready, and gives you the URL.
3
Finish identity provider sign-in (if you chose it)
The agent gives you a callback URL. Register it as the redirect URI with your identity provider, then send the agent the client ID and secret.
No MCP tool creates datastores or attaches buckets. Create the Postgres database first, and attach a bucket afterwards from the add-on’s General settings in the dashboard.
Porter stores the datastore’s connection details in an environment group with the same name, qm-db. In the dashboard, Connect existing datastore fills in its URL for you.
Create an OpenID Connect web app. Set its redirect URI to your QM domain followed by /auth/callback, for example https://qm.acme.com/auth/callback.
2
Choose the provider
Select Your identity provider, then pick a provider. Google fills in its endpoints, and Okta fills them in from your Okta domain. For Custom, copy the issuer and endpoint URLs from your provider’s /.well-known/openid-configuration document.
3
Enter the client ID and secret
Paste them from the app you registered, then click Deploy.
Administrators can always sign in. To let others in, open the advanced settings under People and fill in Allowed sign-in emails or Allowed email domain.
If sign-in fails with a redirect mismatch while the add-on reports healthy, add the callback URL to your provider’s allowed redirect URIs.
Serve QM from the cluster’s private load balancer to make it reachable only from your VPC or networks peered to it, for example over Tailscale.
1
Add the private load balancer to the cluster
Follow Private load balancer in the cluster settings, including the DNS provider Porter uses to issue certificates.
2
Let sandboxes reach QM
Agents run in sandboxes and call back into QM through its URL, but sandboxes can’t reach private addresses by default. In the Sandbox tab, open Settings and enable Enforce egress allowlists under Egress controls. Porter then lets every sandbox reach the QM domain.
3
Select the private load balancer and set a domain
On the add-on’s General step, select Private LB. The form shows the load balancer’s address and the DNS record type to use. Create that record for a custom domain such as qm.internal.acme.com, enter the domain, and click Deploy.The address only resolves inside your VPC. Point your private DNS at it if your clients resolve through one.
To keep published apps private too, don’t configure Public sandbox networking.
Change QM’s settings from the add-on’s Configuration tab, then click Deploy. The tabs match the setup steps, plus Integrations for fallback model API keys and Slack credentials.To upgrade, pick a newer version in the Release picker on the General tab. To reset the password, enter a new one on the Sign-in tab. Allowed emails and an allowed domain are under the advanced settings on the People tab.Secret fields you leave empty keep their current values. The database can’t be changed after install.
Open the QM add-on, go to the Settings tab, and click Delete under Danger zone.
Deleting the QM add-on stops QM and removes its configuration. Your Postgres database and bucket aren’t deleted, so delete them separately if you no longer need the data.
{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"Porter","colors":{"primary":"#5B63B2","light":"#AAB6FF","dark":"#3C4189"},"logo":{"light":"https://mintcdn.com/porter/CtQBkRh1tKAcCdoJ/logo/light.png?fit=max\u0026auto=format\u0026n=CtQBkRh1tKAcCdoJ\u0026q=85\u0026s=5de59d319dc29230f06a5cc0d54518de","dark":"https://mintcdn.com/porter/CtQBkRh1tKAcCdoJ/logo/dark.png?fit=max\u0026auto=format\u0026n=CtQBkRh1tKAcCdoJ\u0026q=85\u0026s=20a106f87d62ff1c1d1cba53466fce37","href":"https://porter.run"},"favicon":"https://mintcdn.com/porter/hk4qFrcZwgdgi3c0/favicon.ico?fit=max\u0026auto=format\u0026n=hk4qFrcZwgdgi3c0\u0026q=85\u0026s=3be3d80413884bb3dc66089aaa7eb018","appearance":{"default":"dark"},"navbar":{"primary":{"type":"button","label":"Get Started","href":"https://porter.run"}},"footer":{"socials":{"twitter":"https://twitter.com/porterdotrun","github":"https://github.com/porter-dev","linkedin":"https://www.linkedin.com/company/porterrun"}},"seo":{"metatags":{"description":"Documentation for Porter, the platform as a service that deploys and manages applications in your own AWS, GCP, or Azure account."},"organization":{"id":"https://porter.run/#organization","name":"Porter","legalName":"Porter Technologies Inc","url":"https://porter.run","logo":"https://docs.porter.run/logo/light.png","sameAs":["https://github.com/porter-dev","https://www.linkedin.com/company/porterrun","https://www.ycombinator.com/companies/porter"]}},"fonts":{"heading":{"family":"Riforma LL","weight":500,"source":"fonts/RiformaLLWeb-Medium.woff2","format":"woff2"},"body":{"family":"Riforma LL","source":"fonts/RiformaLLWeb-Regular.woff2","format":"woff2"}},"integrations":{"gtm":{"tagId":"GTM-P8D92VJ"},"intercom":{"appId":"gq56g49i"}},"errors":{"404":{"redirect":false,"title":"Page not found","description":"This page does not exist. Start from the [documentation home](/), the [machine-readable index](/llms.txt), the [sitemap](https://docs.porter.run/sitemap.xml), or the [introduction](/getting-started/introduction)."}},"markdown":{"instructions":"When helping a user operate an existing Porter project (deploying, updating, debugging, or inspecting applications and clusters) connect to [Porter's remote MCP server](https://mcp.porter.run) first and use its tools before falling back to the Porter CLI, `porter.yaml`, or dashboard workflows. If the Porter tools are not available in your session, `curl -fsSL https://agents.porter.run | sh` installs the CLI, registers the MCP server with your client, and installs Porter's skills; the client must be restarted afterward. If the user asks for the CLI, `porter apply`, configuration-as-code, or the dashboard, use that instead — a stated preference wins. Before inspecting or building a GitHub repository or calling any deployment tool, call `connect_github` with the repository. Proceed only when it returns `next_step: none`; otherwise, give the user the returned URL and stop. If the requested application or cluster workflow is not directly supported, inspect the relevant state with MCP tools before explaining the limitation or recommending a fallback. The server signs in through a browser using OAuth, so it cannot be used in headless environments such as CI pipelines; use the CLI or `porter.yaml` there."},"styling":{"latex":false},"navigation":{"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"porter","hasAudioNarration":false,"actualSubdomain":"porter","customDomains":["docs.porter.run"],"gitSource":{"type":"github","owner":"porter-dev","repo":"docs","deployBranch":"main","contentDirectory":"","isPrivate":true,"mountPath":"","label":""},"entitlements":{"AI_CHAT":{"status":"ENABLED"},"WEB_MCP":{"status":"ENABLED"},"NARRATION":{"status":"ENABLED"}},"buildId":"6af581b455c1ebcca521b7d7","clientVersion":"0.0.4111","searchFilterCounts":[{"language":"default","tag":"Documentation","count":105},{"language":"default","tag":"Applications","count":36},{"language":"default","tag":"Command Line Interface (CLI)","count":14},{"language":"default","tag":"Sandboxes","count":14},{"language":"default","tag":"Command Reference","count":12},{"language":"default","tag":"Deploy","count":11},{"language":"default","tag":"Configuration as Code","count":9},{"language":"default","tag":"Security and Compliance","count":9},{"language":"default","tag":"Configure","count":8},{"language":"default","tag":"Cloud Accounts","count":8}]},"assistantConfig":{},"basePath":"","hasChatPermissions":true,"hasWebMcpPermissions":true,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-rbac-roles","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"rbac-expansion","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"full-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-sso-scim","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-signup","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-dual-write","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-enabled","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"approve-review-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-metadata","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"sso-setup-deadline-banner","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-serve","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"live-agents","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"googleFontsInlined":false},"customCss":[{"filePath":"style.css","content":"@font-face{font-family:\"Riforma LL\";font-style:normal;font-weight:400;font-display:swap;src:url(\"/fonts/RiformaLLWeb-Regular.woff2\") format(\"woff2\")}@font-face{font-family:\"Riforma LL\";font-style:normal;font-weight:500;font-display:swap;src:url(\"/fonts/RiformaLLWeb-Medium.woff2\") format(\"woff2\")}@font-face{font-family:\"Riforma LL\";font-style:normal;font-weight:600 700;font-display:swap;src:url(\"/fonts/RiformaLLWeb-Bold.woff2\") format(\"woff2\")}@font-face{font-family:\"Riforma Mono\";font-style:normal;font-weight:400;font-display:swap;src:url(\"/fonts/RiformaMonoLLWeb-Regular.woff2\") format(\"woff2\")}@font-face{font-family:\"Riforma Mono\";font-style:normal;font-weight:600 700;font-display:swap;src:url(\"/fonts/RiformaMonoLLWeb-Bold.woff2\") format(\"woff2\")}:root{--font-weight-bold: 500;--font-weight-semibold: 500;--font-family-mono-custom: \"Riforma Mono\"}strong,b,th{font-weight:500}a{font-weight:inherit}"}],"page":{"pagePath":"addons/qm","slug":"/addons/qm","pageMetadata":{"title":"QM","sidebarTitle":"QM","description":"Run QM, the open source multiplayer agent harness, on your own Porter cluster with agents in Porter Sandboxes, a Postgres database, and an optional S3 bucket","href":"/addons/qm"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"Run QM, the open source multiplayer agent harness, on your own Porter cluster with agents in Porter Sandboxes, a Postgres database, and an optional S3 bucket\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={a:`a`,code:`code`,li:`li`,p:`p`,pre:`pre`,span:`span`,strong:`strong`,tbody:`tbody`,td:`td`,th:`th`,thead:`thead`,tr:`tr`,ul:`ul`,..._provideComponents(),...props.components},{CodeBlock,Heading,Info,Note,OptimizedImage,Step,Steps,Table,Visibility,Warning}=_components;if(!CodeBlock)_missingMdxReference(`CodeBlock`,true);if(!Heading)_missingMdxReference(`Heading`,true);if(!Info)_missingMdxReference(`Info`,true);if(!Note)_missingMdxReference(`Note`,true);if(!OptimizedImage)_missingMdxReference(`OptimizedImage`,true);if(!Step)_missingMdxReference(`Step`,true);if(!Steps)_missingMdxReference(`Steps`,true);if(!Table)_missingMdxReference(`Table`,true);if(!Visibility)_missingMdxReference(`Visibility`,true);if(!Warning)_missingMdxReference(`Warning`,true);return _jsxs(_Fragment,{children:[_jsxs(_components.p,{children:[_jsx(_components.a,{href:`https://github.com/yc-software/qm`,children:`QM`}),` is an open source, multiplayer agent harness. Your team works with agents in a shared workspace where context and files carry across your organization. Porter runs QM as an add-on on your cluster, and every agent runs in a `,_jsx(_components.a,{href:`/sandboxes/overview`,children:`Porter Sandbox`}),` on the same cluster.`]}),`\n`,_jsxs(_components.p,{children:[`To see what teams build with it, read `,_jsx(_components.a,{href:`/addons/qm/use-cases`,children:`QM use cases`}),`.`]}),`\n`,_jsx(Info,{children:_jsx(_components.p,{children:`QM is currently only available on AWS. If you don’t see QM in the add-on list, reach out via the support widget on the dashboard.`})}),`\n`,_jsx(Heading,{level:`2`,id:`prerequisites`,children:`Prerequisites`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[`An AWS cluster with `,_jsx(_components.a,{href:`/sandboxes/getting-started#enable-sandboxes`,children:`sandboxes enabled`}),`. Porter rejects a QM install on a cluster without them.`]}),`\n`,_jsxs(_components.li,{children:[`A Postgres database reachable from the cluster. A `,_jsx(_components.a,{href:`/addons/datastores`,children:`Porter datastore`}),` works, or bring your own. Use one database per QM install.`]}),`\n`,_jsxs(_components.li,{children:[`Optional: an S3 bucket `,_jsx(_components.a,{href:`/sandboxes/volumes#set-up-the-bucket`,children:`connected to the cluster`}),`, in the same AWS account and not read-only.`]}),`\n`,_jsxs(_components.li,{children:[`The `,_jsx(_components.strong,{children:`Admin`}),` or `,_jsx(_components.strong,{children:`Developer`}),` role in your Porter project.`]}),`\n`]}),`\n`,_jsx(Heading,{level:`2`,id:`deploy-qm`,children:`Deploy QM`}),`\n`,_jsxs(_components.p,{children:[`You can deploy QM from the `,_jsx(_components.a,{href:`#from-the-dashboard`,children:`dashboard`}),`, have an AI agent do it with the `,_jsx(_components.a,{href:`#with-the-porter-mcp-server`,children:`Porter MCP server`}),`, or set up the prerequisites with the `,_jsx(_components.a,{href:`#with-the-porter-cli`,children:`Porter CLI`}),`.`]}),`\n`,_jsxs(Visibility,{for:`humans`,children:[_jsx(Heading,{level:`3`,id:`from-the-dashboard`,children:`From the dashboard`}),_jsxs(Steps,{children:[_jsxs(Step,{title:`Open QM`,children:[_jsxs(_components.p,{children:[`In the Porter dashboard, go to `,_jsx(_components.strong,{children:`Agents`}),` and click `,_jsx(_components.strong,{children:`Deploy QM`}),`.`]}),_jsx(OptimizedImage,{src:`https://mintcdn.com/porter/JL-eGdDiFu0NdFyQ/images/addons/qm/qm-agents-deploy.webp?fit=max\u0026auto=format\u0026n=JL-eGdDiFu0NdFyQ\u0026q=85\u0026s=64670ff49d8e3977d2eb551def72f300`,alt:`Deploy QM on the Agents page`,\"data-og-width\":`2025`,width:`2025`,\"data-og-height\":`776`,height:`776`,\"data-path\":`images/addons/qm/qm-agents-deploy.webp`,\"data-optimize\":`true`,\"data-opv\":`3`,srcset:`https://mintcdn.com/porter/JL-eGdDiFu0NdFyQ/images/addons/qm/qm-agents-deploy.webp?w=280\u0026fit=max\u0026auto=format\u0026n=JL-eGdDiFu0NdFyQ\u0026q=85\u0026s=c7fcda8e58eb694a2499f8cd3786fbc6 280w, https://mintcdn.com/porter/JL-eGdDiFu0NdFyQ/images/addons/qm/qm-agents-deploy.webp?w=560\u0026fit=max\u0026auto=format\u0026n=JL-eGdDiFu0NdFyQ\u0026q=85\u0026s=e894949ec01c657b752a731b822e6868 560w, https://mintcdn.com/porter/JL-eGdDiFu0NdFyQ/images/addons/qm/qm-agents-deploy.webp?w=840\u0026fit=max\u0026auto=format\u0026n=JL-eGdDiFu0NdFyQ\u0026q=85\u0026s=854b0e0938eb9249fd3347de6f5d1a3e 840w, https://mintcdn.com/porter/JL-eGdDiFu0NdFyQ/images/addons/qm/qm-agents-deploy.webp?w=1100\u0026fit=max\u0026auto=format\u0026n=JL-eGdDiFu0NdFyQ\u0026q=85\u0026s=a7a13b79c47da21a0777245a8d28285d 1100w, https://mintcdn.com/porter/JL-eGdDiFu0NdFyQ/images/addons/qm/qm-agents-deploy.webp?w=1650\u0026fit=max\u0026auto=format\u0026n=JL-eGdDiFu0NdFyQ\u0026q=85\u0026s=bc1e3c804c75f834212f7abb6951905a 1650w, https://mintcdn.com/porter/JL-eGdDiFu0NdFyQ/images/addons/qm/qm-agents-deploy.webp?w=2500\u0026fit=max\u0026auto=format\u0026n=JL-eGdDiFu0NdFyQ\u0026q=85\u0026s=9fbcbc35dd3fc16713d1700829fc221d 2500w`})]}),_jsxs(Step,{title:`Pick a cluster, name it, and set up storage`,children:[_jsxs(_components.p,{children:[`Choose the cluster and enter a name: lowercase letters, numbers, and `,_jsx(_components.code,{children:`-`}),`, up to 31 characters. Then fill in `,_jsx(_components.strong,{children:`General`}),`:`]}),_jsx(OptimizedImage,{src:`https://mintcdn.com/porter/G2k1LMgWvLAlYZwq/images/addons/qm/qm-create-general.webp?fit=max\u0026auto=format\u0026n=G2k1LMgWvLAlYZwq\u0026q=85\u0026s=d2d4af341f51a31af7969b71a46ec95d`,alt:`QM general settings`,\"data-og-width\":`620`,width:`620`,\"data-og-height\":`590`,height:`590`,\"data-path\":`images/addons/qm/qm-create-general.webp`,\"data-optimize\":`true`,\"data-opv\":`3`,srcset:`https://mintcdn.com/porter/G2k1LMgWvLAlYZwq/images/addons/qm/qm-create-general.webp?w=280\u0026fit=max\u0026auto=format\u0026n=G2k1LMgWvLAlYZwq\u0026q=85\u0026s=a5c61c4e5fd5b0a76c2482b48221f752 280w, https://mintcdn.com/porter/G2k1LMgWvLAlYZwq/images/addons/qm/qm-create-general.webp?w=560\u0026fit=max\u0026auto=format\u0026n=G2k1LMgWvLAlYZwq\u0026q=85\u0026s=d9f5af47aa776710d6e634a47410a129 560w, https://mintcdn.com/porter/G2k1LMgWvLAlYZwq/images/addons/qm/qm-create-general.webp?w=840\u0026fit=max\u0026auto=format\u0026n=G2k1LMgWvLAlYZwq\u0026q=85\u0026s=de186b3ea68b0af15f1bd71d0ecfd34a 840w, https://mintcdn.com/porter/G2k1LMgWvLAlYZwq/images/addons/qm/qm-create-general.webp?w=1100\u0026fit=max\u0026auto=format\u0026n=G2k1LMgWvLAlYZwq\u0026q=85\u0026s=668834cffc36a8d39003f70755c72e1c 1100w, https://mintcdn.com/porter/G2k1LMgWvLAlYZwq/images/addons/qm/qm-create-general.webp?w=1650\u0026fit=max\u0026auto=format\u0026n=G2k1LMgWvLAlYZwq\u0026q=85\u0026s=6a478c5fb3449603eefab3c01f64de99 1650w, https://mintcdn.com/porter/G2k1LMgWvLAlYZwq/images/addons/qm/qm-create-general.webp?w=2500\u0026fit=max\u0026auto=format\u0026n=G2k1LMgWvLAlYZwq\u0026q=85\u0026s=8c7285d652bf4c8c31378b4fd623d3bb 2500w`}),_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Setting`}),_jsx(_components.th,{children:`What to do`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`Expose QM on a URL`})}),_jsx(_components.td,{children:`Leave it on. Porter gives the portal a domain with TLS. Sign-in and published apps need it.`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`Load balancer`})}),_jsxs(_components.td,{children:[`Only shown if the cluster has a `,_jsx(_components.a,{href:`#private-load-balancer`,children:`private load balancer`}),`. `,_jsx(_components.strong,{children:`Public LB`}),` is the default.`]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`Database`})}),_jsxs(_components.td,{children:[`Click `,_jsx(_components.strong,{children:`Connect existing datastore`}),` to fill in the URL from a Porter datastore, or paste a `,_jsx(_components.code,{children:`postgres://user:password@host:5432/qm`}),` URL.`]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`File storage`})}),_jsxs(_components.td,{children:[`Pick the bucket for uploads, snapshots, and `,_jsx(_components.strong,{children:`Files`}),` data.`]})]})]})]}),_jsx(Warning,{children:_jsx(_components.p,{children:`Without a bucket, QM keeps files in temporary storage, and they’re lost whenever QM restarts or updates. Attach a bucket for anything beyond a trial.`})}),_jsx(Note,{children:_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`Create datastore`}),` and `,_jsx(_components.strong,{children:`Create bucket`}),` leave the form and discard your changes. Create them before you start.`]})})]}),_jsxs(Step,{title:`Choose how people sign in`,children:[_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`Password`}),` is the quickest way to start. Porter fills in your email and generates a password. You can switch to another `,_jsx(_components.a,{href:`#sign-in-methods`,children:`sign-in method`}),` at any time.`]}),_jsx(OptimizedImage,{src:`https://mintcdn.com/porter/G2k1LMgWvLAlYZwq/images/addons/qm/qm-sign-in-provider.webp?fit=max\u0026auto=format\u0026n=G2k1LMgWvLAlYZwq\u0026q=85\u0026s=725fbca4dc8c050c45f39569af0892b2`,alt:`QM sign-in settings with a password account`,\"data-og-width\":`1838`,width:`1838`,\"data-og-height\":`1190`,height:`1190`,\"data-path\":`images/addons/qm/qm-sign-in-provider.webp`,\"data-optimize\":`true`,\"data-opv\":`3`,srcset:`https://mintcdn.com/porter/G2k1LMgWvLAlYZwq/images/addons/qm/qm-sign-in-provider.webp?w=280\u0026fit=max\u0026auto=format\u0026n=G2k1LMgWvLAlYZwq\u0026q=85\u0026s=5d91d993211aead67a2b61b2dd7f28fe 280w, https://mintcdn.com/porter/G2k1LMgWvLAlYZwq/images/addons/qm/qm-sign-in-provider.webp?w=560\u0026fit=max\u0026auto=format\u0026n=G2k1LMgWvLAlYZwq\u0026q=85\u0026s=d4ecc8626c17195da766055e448bcbe7 560w, https://mintcdn.com/porter/G2k1LMgWvLAlYZwq/images/addons/qm/qm-sign-in-provider.webp?w=840\u0026fit=max\u0026auto=format\u0026n=G2k1LMgWvLAlYZwq\u0026q=85\u0026s=954bf105248d6f0f463db61f093cfaf0 840w, https://mintcdn.com/porter/G2k1LMgWvLAlYZwq/images/addons/qm/qm-sign-in-provider.webp?w=1100\u0026fit=max\u0026auto=format\u0026n=G2k1LMgWvLAlYZwq\u0026q=85\u0026s=234e7c78df8041d2ad1300397976bae1 1100w, https://mintcdn.com/porter/G2k1LMgWvLAlYZwq/images/addons/qm/qm-sign-in-provider.webp?w=1650\u0026fit=max\u0026auto=format\u0026n=G2k1LMgWvLAlYZwq\u0026q=85\u0026s=48e3d3545fa8bde7d94d589493cdf577 1650w, https://mintcdn.com/porter/G2k1LMgWvLAlYZwq/images/addons/qm/qm-sign-in-provider.webp?w=2500\u0026fit=max\u0026auto=format\u0026n=G2k1LMgWvLAlYZwq\u0026q=85\u0026s=1451bf78d5ecced5dfe2f565b33a0162 2500w`}),_jsx(Warning,{children:_jsxs(_components.p,{children:[`Copy the password before you click `,_jsx(_components.strong,{children:`Deploy`}),`. Porter stores only a hash of it, so it can’t be shown again.`]})}),_jsx(_components.p,{children:`Once QM is deployed, open the URL in the add-on’s header and sign in with this account. The URL is your custom domain if you set one, otherwise the Porter domain.`}),_jsxs(_components.p,{children:[`QM doesn’t need a model provider to install. After your first sign-in, set the base model in the QM admin console. The add-on’s `,_jsx(_components.strong,{children:`Integrations`}),` tab also takes fallback API keys for Anthropic, OpenAI, and OpenRouter, plus Slack bot credentials.`]})]}),_jsxs(Step,{title:`Add administrators and deploy`,children:[_jsx(_components.p,{children:`Administrators can manage QM and can always sign in. Your email is added for you. Invite everyone else from the QM admin console after first boot.`}),_jsx(OptimizedImage,{src:`https://mintcdn.com/porter/G2k1LMgWvLAlYZwq/images/addons/qm/qm-administrators.webp?fit=max\u0026auto=format\u0026n=G2k1LMgWvLAlYZwq\u0026q=85\u0026s=e05d876d47706bf0e77044c0dacad8a0`,alt:`QM administrators`,\"data-og-width\":`1852`,width:`1852`,\"data-og-height\":`662`,height:`662`,\"data-path\":`images/addons/qm/qm-administrators.webp`,\"data-optimize\":`true`,\"data-opv\":`3`,srcset:`https://mintcdn.com/porter/G2k1LMgWvLAlYZwq/images/addons/qm/qm-administrators.webp?w=280\u0026fit=max\u0026auto=format\u0026n=G2k1LMgWvLAlYZwq\u0026q=85\u0026s=c844076cfad5d91fc7e0abee17209a0c 280w, https://mintcdn.com/porter/G2k1LMgWvLAlYZwq/images/addons/qm/qm-administrators.webp?w=560\u0026fit=max\u0026auto=format\u0026n=G2k1LMgWvLAlYZwq\u0026q=85\u0026s=8674bc2291a2af9e87af814be997b334 560w, https://mintcdn.com/porter/G2k1LMgWvLAlYZwq/images/addons/qm/qm-administrators.webp?w=840\u0026fit=max\u0026auto=format\u0026n=G2k1LMgWvLAlYZwq\u0026q=85\u0026s=8228ac2a1b51076517ed8fad63e256fd 840w, https://mintcdn.com/porter/G2k1LMgWvLAlYZwq/images/addons/qm/qm-administrators.webp?w=1100\u0026fit=max\u0026auto=format\u0026n=G2k1LMgWvLAlYZwq\u0026q=85\u0026s=fa3f64006d4a42af46c7d6f64641b98c 1100w, https://mintcdn.com/porter/G2k1LMgWvLAlYZwq/images/addons/qm/qm-administrators.webp?w=1650\u0026fit=max\u0026auto=format\u0026n=G2k1LMgWvLAlYZwq\u0026q=85\u0026s=2e72cd8ab2105836f39e745716c1288a 1650w, https://mintcdn.com/porter/G2k1LMgWvLAlYZwq/images/addons/qm/qm-administrators.webp?w=2500\u0026fit=max\u0026auto=format\u0026n=G2k1LMgWvLAlYZwq\u0026q=85\u0026s=6ddfd476cd25f210829eeed660116a86 2500w`}),_jsxs(_components.p,{children:[`Click `,_jsx(_components.strong,{children:`Deploy`}),`.`]})]})]})]}),`\n`,_jsxs(Visibility,{for:`agents`,children:[_jsxs(_components.p,{children:[`Run these `,_jsx(_components.a,{href:`/mcp/overview`,children:`Porter MCP`}),` tool calls in order. Pass `,_jsx(_components.code,{children:`project`}),` and `,_jsx(_components.code,{children:`cluster`}),` by ID.`]}),_jsx(_components.p,{children:_jsxs(_components.strong,{children:[`1. Pick the project and an AWS cluster with status `,_jsx(_components.code,{children:`READY`}),`.`]})}),_jsx(CodeBlock,{filename:``,numberOfLines:`2`,language:`json`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`json`,children:_jsxs(_components.code,{language:`json`,numberOfLines:`2`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`get_projects {}`})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`get_clusters { `}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:`\"project\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`123`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` }`})]}),`\n`]})})}),_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`2. Enable sandboxes on the cluster.`}),` QM runs every agent in a sandbox, and the install is rejected with `,_jsx(_components.code,{children:`qm requires sandbox-api`}),` without them. The call is safe to repeat. Poll `,_jsx(_components.code,{children:`get_sandbox_settings`}),` until `,_jsx(_components.code,{children:`ready`}),` is `,_jsx(_components.code,{children:`true`}),`.`]}),_jsx(CodeBlock,{filename:``,numberOfLines:`2`,language:`json`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`json`,children:_jsxs(_components.code,{language:`json`,numberOfLines:`2`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`enable_sandboxes { `}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:`\"project\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`123`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:`\"cluster\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`456`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` }`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`get_sandbox_settings { `}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:`\"project\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`123`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:`\"cluster\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`456`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` }`})]}),`\n`]})})}),_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`3. Get a Postgres URL.`}),` No MCP tool creates or lists datastores. Ask the user for the connection string of a `,_jsx(_components.a,{href:`/addons/datastores`,children:`Porter datastore`}),` (shown on the datastore’s page in the dashboard) or of their own Postgres, reachable from the cluster. Use one database per QM install.`]}),_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`4. Check for an existing QM addon.`}),` If one with type `,_jsx(_components.code,{children:`qm`}),` exists, act on its status instead of installing again.`]}),_jsx(CodeBlock,{filename:``,numberOfLines:`1`,language:`json`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`json`,children:_jsxs(_components.code,{language:`json`,numberOfLines:`1`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`get_addons { `}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:`\"project\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`123`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:`\"cluster\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`456`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` }`})]}),`\n`]})})}),_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`5. Install QM once.`}),` Builtin sign-in emails one-time links through Resend:`]}),_jsx(CodeBlock,{filename:``,numberOfLines:`15`,language:`json`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`json`,children:_jsxs(_components.code,{language:`json`,numberOfLines:`15`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`install_addon {`})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"project\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`123`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"cluster\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`456`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"name\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"qm\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"qm\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: {`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"admins\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: [`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"admin@acme.com\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`],`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"database_url\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"postgres://user:password@host:5432/qm\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"auth\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: {`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"mode\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"builtin\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"email_from\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"QM \u003cnoreply@acme.com>\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"resend_api_key\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"re_...\"`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` },`})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"anthropic_api_key\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"sk-ant-...\"`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` }`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`}`})}),`\n`]})})}),_jsxs(_components.p,{children:[_jsx(_components.code,{children:`anthropic_api_key`}),` is optional: without it, an admin sets a model provider in QM after first sign-in. Optional fields: `,_jsx(_components.code,{children:`custom_domain`}),`, `,_jsx(_components.code,{children:`load_balancer`}),` (`,_jsx(_components.code,{children:`public`}),` or `,_jsx(_components.code,{children:`private`}),`; `,_jsx(_components.code,{children:`private`}),` needs `,_jsx(_components.code,{children:`custom_domain`}),`), and `,_jsx(_components.code,{children:`version`}),`. The MCP install can’t attach an S3 bucket, so files are lost when QM restarts. Attach one afterwards from the add-on’s `,_jsx(_components.strong,{children:`General`}),` settings in the dashboard.`]}),_jsxs(_components.p,{children:[`For an OpenID Connect provider, set `,_jsx(_components.code,{children:`auth.mode`}),` to `,_jsx(_components.code,{children:`oidc`}),` and fill `,_jsx(_components.code,{children:`auth.oidc`}),` from `,_jsx(_components.code,{children:`\u003cissuer>/.well-known/openid-configuration`}),`. Leave out the client ID and secret:`]}),_jsx(CodeBlock,{filename:``,numberOfLines:`11`,language:`json`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`json`,children:_jsxs(_components.code,{language:`json`,numberOfLines:`11`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"auth\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: {`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"mode\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"oidc\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"oidc\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: {`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"issuer\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"https://accounts.google.com\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"authorization_endpoint\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"https://accounts.google.com/o/oauth2/v2/auth\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"token_endpoint\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"https://oauth2.googleapis.com/token\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"userinfo_endpoint\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"https://openidconnect.googleapis.com/v1/userinfo\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"jwks_uri\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"https://www.googleapis.com/oauth2/v3/certs\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"allowed_email_domain\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"acme.com\"`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` }`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`}`})}),`\n`]})})}),_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`6. OIDC only: finish sign-in.`}),` The install returns `,_jsx(_components.code,{children:`oidc_callback_url`}),`. Have the user register it as the redirect URI with their provider, then send the client ID and secret without `,_jsx(_components.code,{children:`auth.mode`}),`:`]}),_jsx(CodeBlock,{filename:``,numberOfLines:`6`,language:`json`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`json`,children:_jsxs(_components.code,{language:`json`,numberOfLines:`6`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`update_addon {`})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"project\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`123`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"cluster\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`456`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"name\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"qm\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"qm\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: { `}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:`\"auth\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: { `}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:`\"oidc\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: { `}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:`\"client_id\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"...\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:`\"client_secret\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"...\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` } } }`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`}`})}),`\n`]})})}),_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`7. Wait for QM.`}),` Poll every 30 to 60 seconds, for up to 10 minutes. QM is ready when `,_jsx(_components.code,{children:`status`}),` is `,_jsx(_components.code,{children:`installed`}),` and every service (`,_jsx(_components.code,{children:`core`}),`, `,_jsx(_components.code,{children:`egress-proxy`}),`, `,_jsx(_components.code,{children:`portal`}),`, `,_jsx(_components.code,{children:`web-ui`}),`) has `,_jsx(_components.code,{children:`ready_replicas`}),` equal to `,_jsx(_components.code,{children:`desired_replicas`}),`. `,_jsx(_components.code,{children:`waiting_for_sso`}),` means step 6 is still needed.`]}),_jsx(CodeBlock,{filename:``,numberOfLines:`1`,language:`json`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`json`,children:_jsxs(_components.code,{language:`json`,numberOfLines:`1`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`get_addon_status { `}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:`\"project\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`123`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:`\"cluster\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`456`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:`\"name\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"qm\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` }`})]}),`\n`]})})}),_jsxs(_components.p,{children:[`Give the user `,_jsx(_components.code,{children:`qm.url`}),` from the install response. A `,_jsx(_components.code,{children:`401`}),` from that URL means QM is up and asking for sign-in.`]})]}),`\n`,_jsx(Heading,{level:`3`,id:`with-the-porter-mcp-server`,children:`With the Porter MCP server`}),`\n`,_jsxs(_components.p,{children:[`The `,_jsx(_components.a,{href:`/mcp/overview`,children:`Porter MCP server`}),` lets an AI agent such as Claude Code or Cursor install and manage QM for you.`]}),`\n`,_jsxs(Steps,{children:[_jsxs(Step,{title:`Install the Porter MCP server and skills`,children:[_jsx(CodeBlock,{filename:``,numberOfLines:`1`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`1`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`curl`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` -fsSL`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` https://agents.porter.run`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:` |`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:` sh`})]}),`\n`]})})}),_jsxs(_components.p,{children:[`Restart your agent afterward. The install adds the `,_jsx(_components.code,{children:`porter-qm`}),` skill, which takes a cluster to a signed-in QM install.`]})]}),_jsxs(Step,{title:`Ask your agent to set up QM`,children:[_jsx(_components.p,{children:`For example:`}),_jsx(CodeBlock,{filename:``,numberOfLines:`2`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`2`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Set up QM on my Porter cluster. Use this Postgres database: postgres://user:password@host:5432/qm`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),_jsx(_components.p,{children:`The agent asks for anything it still needs in one round of questions: the cluster, administrators, and how people sign in. It then enables sandboxes if the cluster doesn’t have them, installs QM, waits for it to be ready, and gives you the URL.`})]}),_jsx(Step,{title:`Finish identity provider sign-in (if you chose it)`,children:_jsx(_components.p,{children:`The agent gives you a callback URL. Register it as the redirect URI with your identity provider, then send the agent the client ID and secret.`})})]}),`\n`,_jsx(Note,{children:_jsxs(_components.p,{children:[`No MCP tool creates datastores or attaches buckets. Create the Postgres database first, and attach a bucket afterwards from the add-on’s `,_jsx(_components.strong,{children:`General`}),` settings in the dashboard.`]})}),`\n`,_jsx(Heading,{level:`3`,id:`with-the-porter-cli`,children:`With the Porter CLI`}),`\n`,_jsxs(_components.p,{children:[`The Porter CLI can’t create the QM add-on itself yet, but it can set up everything QM needs. Then finish from the `,_jsx(_components.a,{href:`#from-the-dashboard`,children:`dashboard`}),` or with the `,_jsx(_components.a,{href:`#with-the-porter-mcp-server`,children:`MCP server`}),`.`]}),`\n`,_jsxs(Steps,{children:[_jsx(Step,{title:`Select the cluster`,children:_jsx(CodeBlock,{filename:``,numberOfLines:`1`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`1`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`porter`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` config`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` set-cluster`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:` \u003c`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`CLUSTER_I`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`D`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`>`})]}),`\n`]})})})}),_jsx(Step,{title:`Enable sandboxes`,children:_jsx(CodeBlock,{filename:``,numberOfLines:`1`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`1`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`porter`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` sandbox`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` enable`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` --wait`})]}),`\n`]})})})}),_jsxs(Step,{title:`Create a Postgres datastore`,children:[_jsxs(_components.p,{children:[`Add a Postgres add-on to a `,_jsx(_components.code,{children:`porter.yaml`}),` and apply it. See `,_jsx(_components.a,{href:`/applications/configuration-as-code/addons-porter-yaml`,children:`Add-ons in porter.yaml`}),` for the options.`]}),_jsx(CodeBlock,{filename:`porter.yaml`,numberOfLines:`8`,language:`yaml`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`yaml`,children:_jsxs(_components.code,{language:`yaml`,numberOfLines:`8`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#569CD6`},children:`addons`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`:`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` - `}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#569CD6`},children:`name`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`qm-db`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#569CD6`},children:` type`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`postgres`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#569CD6`},children:` kind`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`in-cluster`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#569CD6`},children:` config`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`:`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#569CD6`},children:` storageGigabytes`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`10`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#569CD6`},children:` cpuCores`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`0.5`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#569CD6`},children:` ramMegabytes`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`1024`})]}),`\n`]})})}),_jsx(CodeBlock,{filename:``,numberOfLines:`1`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`1`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`porter`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` apply`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` -f`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` porter.yaml`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` --wait`})]}),`\n`]})})}),_jsxs(_components.p,{children:[`Porter stores the datastore’s connection details in an environment group with the same name, `,_jsx(_components.code,{children:`qm-db`}),`. In the dashboard, `,_jsx(_components.strong,{children:`Connect existing datastore`}),` fills in its URL for you.`]})]})]}),`\n`,_jsx(Heading,{level:`2`,id:`sign-in-methods`,children:`Sign-in methods`}),`\n`,_jsxs(_components.p,{children:[`Change the method at any time from the add-on’s `,_jsx(_components.strong,{children:`Sign-in`}),` tab.`]}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Method`}),_jsx(_components.th,{children:`How people sign in`}),_jsx(_components.th,{children:`What you need`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`Password`})}),_jsx(_components.td,{children:`One account with an email and password. Good for getting started.`}),_jsx(_components.td,{children:`A password of at least 12 characters`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`Your identity provider`})}),_jsx(_components.td,{children:`Through an OpenID Connect provider such as Google Workspace, Okta, or Entra.`}),_jsx(_components.td,{children:`An OAuth client ID and secret`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`Email links from QM`})}),_jsx(_components.td,{children:`QM emails one-time sign-in links to allowed addresses.`}),_jsxs(_components.td,{children:[`A sender verified with `,_jsx(_components.a,{href:`https://resend.com`,children:`Resend`}),` and a Resend API key`]})]})]})]}),`\n`,_jsx(Heading,{level:`3`,id:`use-your-identity-provider`,children:`Use your identity provider`}),`\n`,_jsxs(Steps,{children:[_jsx(Step,{title:`Register an app with your provider`,children:_jsxs(_components.p,{children:[`Create an OpenID Connect web app. Set its redirect URI to your QM domain followed by `,_jsx(_components.code,{children:`/auth/callback`}),`, for example `,_jsx(_components.code,{children:`https://qm.acme.com/auth/callback`}),`.`]})}),_jsx(Step,{title:`Choose the provider`,children:_jsxs(_components.p,{children:[`Select `,_jsx(_components.strong,{children:`Your identity provider`}),`, then pick a provider. `,_jsx(_components.strong,{children:`Google`}),` fills in its endpoints, and `,_jsx(_components.strong,{children:`Okta`}),` fills them in from your Okta domain. For `,_jsx(_components.strong,{children:`Custom`}),`, copy the issuer and endpoint URLs from your provider’s `,_jsx(_components.code,{children:`/.well-known/openid-configuration`}),` document.`]})}),_jsx(Step,{title:`Enter the client ID and secret`,children:_jsxs(_components.p,{children:[`Paste them from the app you registered, then click `,_jsx(_components.strong,{children:`Deploy`}),`.`]})})]}),`\n`,_jsxs(_components.p,{children:[`Administrators can always sign in. To let others in, open the advanced settings under `,_jsx(_components.strong,{children:`People`}),` and fill in `,_jsx(_components.strong,{children:`Allowed sign-in emails`}),` or `,_jsx(_components.strong,{children:`Allowed email domain`}),`.`]}),`\n`,_jsx(Note,{children:_jsx(_components.p,{children:`If sign-in fails with a redirect mismatch while the add-on reports healthy, add the callback URL to your provider’s allowed redirect URIs.`})}),`\n`,_jsx(Heading,{level:`2`,id:`private-load-balancer`,children:`Private load balancer`}),`\n`,_jsxs(_components.p,{children:[`Serve QM from the cluster’s `,_jsx(_components.a,{href:`/cloud-accounts/advanced-cluster-settings#private-load-balancer`,children:`private load balancer`}),` to make it reachable only from your VPC or networks peered to it, for example over `,_jsx(_components.a,{href:`/security-and-compliance/tailscale`,children:`Tailscale`}),`.`]}),`\n`,_jsxs(Steps,{children:[_jsx(Step,{title:`Add the private load balancer to the cluster`,children:_jsxs(_components.p,{children:[`Follow `,_jsx(_components.a,{href:`/cloud-accounts/advanced-cluster-settings#private-load-balancer`,children:`Private load balancer`}),` in the cluster settings, including the DNS provider Porter uses to issue certificates.`]})}),_jsx(Step,{title:`Let sandboxes reach QM`,children:_jsxs(_components.p,{children:[`Agents run in sandboxes and call back into QM through its URL, but sandboxes can’t reach private addresses by default. In the `,_jsx(_components.strong,{children:`Sandbox`}),` tab, open `,_jsx(_components.strong,{children:`Settings`}),` and enable `,_jsx(_components.strong,{children:`Enforce egress allowlists`}),` under `,_jsx(_components.strong,{children:`Egress controls`}),`. Porter then lets every sandbox reach the QM domain.`]})}),_jsxs(Step,{title:`Select the private load balancer and set a domain`,children:[_jsxs(_components.p,{children:[`On the add-on’s `,_jsx(_components.strong,{children:`General`}),` step, select `,_jsx(_components.strong,{children:`Private LB`}),`. The form shows the load balancer’s address and the DNS record type to use. Create that record for a custom domain such as `,_jsx(_components.code,{children:`qm.internal.acme.com`}),`, enter the domain, and click `,_jsx(_components.strong,{children:`Deploy`}),`.`]}),_jsx(_components.p,{children:`The address only resolves inside your VPC. Point your private DNS at it if your clients resolve through one.`})]})]}),`\n`,_jsxs(_components.p,{children:[`To keep published apps private too, don’t configure `,_jsx(_components.strong,{children:`Public`}),` sandbox networking.`]}),`\n`,_jsx(Heading,{level:`2`,id:`published-apps`,children:`Published apps`}),`\n`,_jsxs(_components.p,{children:[`Agents can publish the apps they build. Each app runs in its own sandbox. Where it’s served depends on the cluster’s `,_jsx(_components.a,{href:`/sandboxes/networking`,children:`sandbox networking`}),`:`]}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Sandbox networking`}),_jsx(_components.th,{children:`Where published apps are served`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`None`}),_jsxs(_components.td,{children:[`Inside QM only, at `,_jsx(_components.code,{children:`\u003cQM domain>/d/\u003capp>/`})]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Private`}),_jsx(_components.td,{children:`A hostname under the private sandbox domain, reachable from your VPC`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Public`}),_jsx(_components.td,{children:`A hostname under the public sandbox domain. QM picks public when both are configured.`})]})]})]}),`\n`,_jsxs(_components.p,{children:[`To give apps their own hostnames, follow `,_jsx(_components.a,{href:`/sandboxes/networking#configure-networking-on-the-cluster`,children:`Configure networking on the cluster`}),` through to the wildcard DNS record.`]}),`\n`,_jsx(Heading,{level:`2`,id:`manage-and-update-qm`,children:`Manage and update QM`}),`\n`,_jsxs(_components.p,{children:[`Change QM’s settings from the add-on’s `,_jsx(_components.strong,{children:`Configuration`}),` tab, then click `,_jsx(_components.strong,{children:`Deploy`}),`. The tabs match the setup steps, plus `,_jsx(_components.strong,{children:`Integrations`}),` for fallback model API keys and Slack credentials.`]}),`\n`,_jsxs(_components.p,{children:[`To upgrade, pick a newer version in the `,_jsx(_components.strong,{children:`Release`}),` picker on the `,_jsx(_components.strong,{children:`General`}),` tab. To reset the password, enter a new one on the `,_jsx(_components.strong,{children:`Sign-in`}),` tab. Allowed emails and an allowed domain are under the advanced settings on the `,_jsx(_components.strong,{children:`People`}),` tab.`]}),`\n`,_jsx(_components.p,{children:`Secret fields you leave empty keep their current values. The database can’t be changed after install.`}),`\n`,_jsx(Heading,{level:`2`,id:`troubleshooting`,children:`Troubleshooting`}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Problem`}),_jsx(_components.th,{children:`Fix`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsxs(_components.td,{children:[`Install rejected with `,_jsx(_components.code,{children:`qm requires sandbox-api`})]}),_jsx(_components.td,{children:`Enable sandboxes on the cluster, then deploy again.`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Uploads disappear after an update`}),_jsxs(_components.td,{children:[`QM has no bucket. Attach one under `,_jsx(_components.strong,{children:`File storage`}),`.`]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Agents can’t reach a private QM`}),_jsxs(_components.td,{children:[`Turn on `,_jsx(_components.strong,{children:`Enforce egress allowlists`}),` for the cluster’s sandboxes.`]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Pods never become ready`}),_jsx(_components.td,{children:`Check that the database URL is reachable from inside the cluster.`})]})]})]}),`\n`,_jsx(Heading,{level:`2`,id:`deleting-qm`,children:`Deleting QM`}),`\n`,_jsxs(_components.p,{children:[`Open the QM add-on, go to the `,_jsx(_components.strong,{children:`Settings`}),` tab, and click `,_jsx(_components.strong,{children:`Delete`}),` under `,_jsx(_components.strong,{children:`Danger zone`}),`.`]}),`\n`,_jsx(OptimizedImage,{src:`https://mintcdn.com/porter/G2k1LMgWvLAlYZwq/images/addons/qm/qm-delete.webp?fit=max\u0026auto=format\u0026n=G2k1LMgWvLAlYZwq\u0026q=85\u0026s=49466ebe8915b9b7419a3d77713268f2`,alt:`Deleting QM from the Settings tab`,\"data-og-width\":`2478`,width:`2478`,\"data-og-height\":`300`,height:`300`,\"data-path\":`images/addons/qm/qm-delete.webp`,\"data-optimize\":`true`,\"data-opv\":`3`,srcset:`https://mintcdn.com/porter/G2k1LMgWvLAlYZwq/images/addons/qm/qm-delete.webp?w=280\u0026fit=max\u0026auto=format\u0026n=G2k1LMgWvLAlYZwq\u0026q=85\u0026s=a8ec63c811c4e7c9eb5557ddb291e8ff 280w, https://mintcdn.com/porter/G2k1LMgWvLAlYZwq/images/addons/qm/qm-delete.webp?w=560\u0026fit=max\u0026auto=format\u0026n=G2k1LMgWvLAlYZwq\u0026q=85\u0026s=5ad6eb9de9b7fd8fc2adc3b22ead2893 560w, https://mintcdn.com/porter/G2k1LMgWvLAlYZwq/images/addons/qm/qm-delete.webp?w=840\u0026fit=max\u0026auto=format\u0026n=G2k1LMgWvLAlYZwq\u0026q=85\u0026s=e28a5f4143c2f07dcf9e64c6e18aa1f1 840w, https://mintcdn.com/porter/G2k1LMgWvLAlYZwq/images/addons/qm/qm-delete.webp?w=1100\u0026fit=max\u0026auto=format\u0026n=G2k1LMgWvLAlYZwq\u0026q=85\u0026s=d50dcbeaf68e1a66388404363528473f 1100w, https://mintcdn.com/porter/G2k1LMgWvLAlYZwq/images/addons/qm/qm-delete.webp?w=1650\u0026fit=max\u0026auto=format\u0026n=G2k1LMgWvLAlYZwq\u0026q=85\u0026s=18d6ca618462afad6878b65d4bcceafc 1650w, https://mintcdn.com/porter/G2k1LMgWvLAlYZwq/images/addons/qm/qm-delete.webp?w=2500\u0026fit=max\u0026auto=format\u0026n=G2k1LMgWvLAlYZwq\u0026q=85\u0026s=5465ac7bb354882f230bff06c7381a5f 2500w`}),`\n`,_jsx(Warning,{children:_jsx(_components.p,{children:`Deleting the QM add-on stops QM and removes its configuration. Your Postgres database and bucket aren’t deleted, so delete them separately if you no longer need the data.`})})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"config":{},"pageMetadata":{"title":"QM","sidebarTitle":"QM","description":"Run QM, the open source multiplayer agent harness, on your own Porter cluster with agents in Porter Sandboxes, a Postgres database, and an optional S3 bucket","href":"/addons/qm"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"Prerequisites","slug":"prerequisites","depth":2,"children":[]},{"title":"Deploy QM","slug":"deploy-qm","depth":2,"children":[{"title":"From the dashboard","slug":"from-the-dashboard","depth":3,"children":[]},{"title":"With the Porter MCP server","slug":"with-the-porter-mcp-server","depth":3,"children":[]},{"title":"With the Porter CLI","slug":"with-the-porter-cli","depth":3,"children":[]}]},{"title":"Sign-in methods","slug":"sign-in-methods","depth":2,"children":[{"title":"Use your identity provider","slug":"use-your-identity-provider","depth":3,"children":[]}]},{"title":"Private load balancer","slug":"private-load-balancer","depth":2,"children":[]},{"title":"Published apps","slug":"published-apps","depth":2,"children":[]},{"title":"Manage and update QM","slug":"manage-and-update-qm","depth":2,"children":[]},{"title":"Troubleshooting","slug":"troubleshooting","depth":2,"children":[]},{"title":"Deleting QM","slug":"deleting-qm","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":[],"subdomain":"porter","siteName":"Porter","title":"QM - Porter","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-11-12T09:07:08.822Z","originalFileLocation":"addons/qm.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"dark\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"Run QM, the open source multiplayer agent harness, on your own Porter cluster with agents in Porter Sandboxes, a Postgres database, and an optional S3 bucket\">\u003cmeta data-mint-head name=\"application-name\" content=\"Porter\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/porter/NTslyGGtUJjpK6WT/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/porter/NTslyGGtUJjpK6WT/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/porter/NTslyGGtUJjpK6WT/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/porter/NTslyGGtUJjpK6WT/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/porter/NTslyGGtUJjpK6WT/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/porter/NTslyGGtUJjpK6WT/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/porter/NTslyGGtUJjpK6WT/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/porter/NTslyGGtUJjpK6WT/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/porter/NTslyGGtUJjpK6WT/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"QM - Porter\">\u003cmeta data-mint-head property=\"og:description\" content=\"Run QM, the open source multiplayer agent harness, on your own Porter cluster with agents in Porter Sandboxes, a Postgres database, and an optional S3 bucket\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"Porter\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://docs.porter.run/addons/qm\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://porter.mintlify.site/_mintlify/api/og/v2?division=Documentation\u0026amp;title=QM\u0026amp;description=Run+QM%2C+the+open+source+multiplayer+agent+harness%2C+on+your+own+Porter+cluster+with+agents+in+Porter+Sandboxes%2C+a+Postgres+database%2C+and+an+optional+S3+bucket\u0026amp;theme=cf350358c3c9d56aaa27febb\u0026amp;signature=bd06894685d9134ced534dc5e82f97cfc877234291b23f77a59571b5899ddc16\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"QM - Porter\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"Run QM, the open source multiplayer agent harness, on your own Porter cluster with agents in Porter Sandboxes, a Postgres database, and an optional S3 bucket\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://porter.mintlify.site/_mintlify/api/og/v2?division=Documentation\u0026amp;title=QM\u0026amp;description=Run+QM%2C+the+open+source+multiplayer+agent+harness%2C+on+your+own+Porter+cluster+with+agents+in+Porter+Sandboxes%2C+a+Postgres+database%2C+and+an+optional+S3+bucket\u0026amp;theme=cf350358c3c9d56aaa27febb\u0026amp;signature=bd06894685d9134ced534dc5e82f97cfc877234291b23f77a59571b5899ddc16\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/porter/NTslyGGtUJjpK6WT/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"Porter\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#5B63B2\">\u003clink data-mint-head rel=\"canonical\" href=\"https://docs.porter.run/addons/qm\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/addons/qm.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://porter.run/#organization\",\"name\":\"Porter\",\"legalName\":\"Porter Technologies Inc\",\"url\":\"https://porter.run\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://docs.porter.run/logo/light.png\"},\"sameAs\":[\"https://github.com/porter-dev\",\"https://www.linkedin.com/company/porterrun\",\"https://www.ycombinator.com/companies/porter\"]},{\"@type\":\"WebSite\",\"@id\":\"https://docs.porter.run#website\",\"name\":\"Porter\",\"url\":\"https://docs.porter.run\",\"publisher\":{\"@id\":\"https://porter.run/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://docs.porter.run/addons/qm#webpage\",\"url\":\"https://docs.porter.run/addons/qm\",\"name\":\"QM\",\"description\":\"Run QM, the open source multiplayer agent harness, on your own Porter cluster with agents in Porter Sandboxes, a Postgres database, and an optional S3 bucket\",\"dateModified\":\"2026-11-12T09:07:08.822Z\",\"isPartOf\":{\"@id\":\"https://docs.porter.run#website\"},\"breadcrumb\":{\"@id\":\"https://docs.porter.run/addons/qm#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://docs.porter.run/addons/qm#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Documentation\",\"item\":\"https://docs.porter.run\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Add-ons\",\"item\":\"https://docs.porter.run/addons/overview\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"QM\",\"item\":\"https://docs.porter.run/addons/qm\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://docs.porter.run/addons/qm#article\",\"headline\":\"QM\",\"name\":\"QM\",\"description\":\"Run QM, the open source multiplayer agent harness, on your own Porter cluster with agents in Porter Sandboxes, a Postgres database, and an optional S3 bucket\",\"url\":\"https://docs.porter.run/addons/qm\",\"mainEntityOfPage\":{\"@id\":\"https://docs.porter.run/addons/qm#webpage\"},\"dateModified\":\"2026-11-12T09:07:08.822Z\",\"publisher\":{\"@id\":\"https://porter.run/#organization\"},\"isPartOf\":{\"@id\":\"https://docs.porter.run#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"Porter\",\"publisher\":{\"@type\":\"Organization\",\"name\":\"Porter\"},\"provider\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","agentPointerHref":"/addons/qm.md","siteName":"Porter","nav":{"global":null,"groups":[{"group":"Documentation","pages":[{"title":"Overview","sidebarTitle":"Overview","href":"/index"},{"group":"Getting Started","pages":[{"title":"Introduction","href":"/getting-started/introduction"},{"title":"Concepts","href":"/getting-started/concepts"},{"title":"Quickstart","href":"/getting-started/quickstart"}]},{"group":"AI Agents \u0026 MCP","pages":[{"title":"MCP Server","sidebarTitle":"MCP Server","href":"/mcp/overview"},{"title":"MCP Server Tools","sidebarTitle":"Tools","href":"/mcp/tools"}]},{"group":"Cloud Accounts","pages":[{"title":"Cloud accounts","sidebarTitle":"Overview","href":"/cloud-accounts/overview"},{"title":"Connecting a cloud account","sidebarTitle":"Connecting a Cloud Account","href":"/cloud-accounts/connecting-a-cloud-account"},{"title":"Creating a cluster","sidebarTitle":"Creating a Cluster","href":"/cloud-accounts/creating-a-cluster"},{"title":"Node groups","sidebarTitle":"Node Groups","href":"/cloud-accounts/node-groups"},{"title":"Cluster upgrades","sidebarTitle":"Cluster Upgrades","href":"/cloud-accounts/cluster-upgrades"},{"title":"Advanced Cluster Settings","href":"/cloud-accounts/advanced-cluster-settings"},{"title":"Cluster observability","sidebarTitle":"Cluster Observability","href":"/cloud-accounts/cluster-observability"},{"title":"Deleting a cluster","sidebarTitle":"Deleting a Cluster","href":"/cloud-accounts/deleting-a-cluster"}]},{"group":"Applications","pages":[{"group":"Deploy","pages":[{"title":"Deploying applications","href":"/applications/deploy/overview"},{"title":"Connect GitHub","href":"/applications/deploy/connect-github"},{"title":"Types of services","href":"/applications/deploy/types-of-services"},{"title":"Deploy from a GitHub repository","href":"/applications/deploy/deploy-from-github-repo"},{"title":"Deploy from a Docker registry","href":"/applications/deploy/deploy-from-docker-registry"},{"title":"Configuring application services","href":"/applications/deploy/configuring-application-services"},{"title":"Building your application","href":"/applications/deploy/builds"},{"title":"Deploy multiple apps from one build","href":"/applications/deploy/multiple-deploys-from-same-build"},{"title":"Pre-deploy jobs","href":"/applications/deploy/pre-deploy-jobs"},{"title":"Rollbacks","href":"/applications/deploy/rollbacks"},{"title":"Using other CI tools","href":"/applications/deploy/using-other-ci-tools"},{"title":"Cloning an application","hidden":true,"href":"/applications/deploy/duplicating-apps"}]},{"group":"Configuration as Code","pages":[{"title":"Configuration as code with porter.yaml","sidebarTitle":"Overview","href":"/applications/configuration-as-code/overview"},{"title":"porter.yaml reference","sidebarTitle":"Reference","href":"/applications/configuration-as-code/reference"},{"title":"Deploying multiple apps with porter.yaml","href":"/applications/configuration-as-code/addons-porter-yaml"},{"group":"Service Configuration","pages":[{"title":"Web services in porter.yaml","href":"/applications/configuration-as-code/services/web-service"},{"title":"Worker services in porter.yaml","href":"/applications/configuration-as-code/services/worker-service"},{"title":"Job services in porter.yaml","href":"/applications/configuration-as-code/services/job-service"},{"title":"Predeploy in porter.yaml","href":"/applications/configuration-as-code/services/predeploy"},{"title":"Autoscaling in porter.yaml","sidebarTitle":"Autoscaling","href":"/applications/configuration-as-code/services/autoscaling"},{"title":"Connections in porter.yaml","sidebarTitle":"Connections","href":"/applications/configuration-as-code/services/connections"}]}]},{"group":"Environment Groups","pages":[{"title":"Environment groups","href":"/applications/configure/environment-groups"}]},{"group":"Configure","pages":[{"title":"Basic configuration","href":"/applications/configure/basic-configuration"},{"title":"Autoscaling","href":"/applications/configure/autoscaling"},{"title":"Temporal autoscaling","href":"/applications/configure/temporal-autoscaling"},{"title":"HTTPS certificates and custom domains","href":"/applications/configure/custom-domains"},{"title":"Zero-downtime deployments","href":"/applications/configure/zero-downtime-deployments"},{"title":"Advanced networking","href":"/applications/configure/advanced-networking"},{"title":"Network restriction","href":"/applications/configure/network-restriction"},{"title":"Secure cloud access","href":"/applications/configure/secure-cloud-access"}]},{"group":"Observability","pages":[{"title":"Monitoring","href":"/applications/observability/monitoring"},{"title":"Logging","href":"/applications/observability/logging"},{"title":"Alerts","href":"/applications/observability/alerts"},{"title":"App metadata environment variables","href":"/applications/observability/app-metadata"},{"title":"Custom metrics autoscaling","href":"/applications/observability/custom-metrics-and-autoscaling"},{"title":"DevOps Agent","sidebarTitle":"DevOps Agent","href":"/applications/observability/devops-agent"}]},{"group":"Debug","pages":[{"title":"Common errors","href":"/applications/debug/common-errors"}]}]},{"group":"Sandboxes","pages":[{"title":"Sandboxes Overview","href":"/sandboxes/overview"},{"title":"Sandboxes Getting Started","href":"/sandboxes/getting-started"},{"title":"Volumes","href":"/sandboxes/volumes"},{"title":"Sandbox Networking","href":"/sandboxes/networking"},{"title":"Sandbox Capacity","href":"/sandboxes/capacity"},{"group":"Sandbox SDK","pages":[{"group":"Python","pages":[{"title":"Python Sandbox SDK quickstart","href":"/sandboxes/sdk/python/quickstart"},{"title":"Python Sandbox SDK volumes","href":"/sandboxes/sdk/python/volumes"},{"title":"Python Sandbox SDK reference","href":"/sandboxes/sdk/python/reference"},{"title":"Python Sandbox SDK errors","href":"/sandboxes/sdk/python/errors"}]},{"group":"TypeScript","pages":[{"title":"TypeScript Sandbox SDK quickstart","href":"/sandboxes/sdk/typescript/quickstart"},{"title":"TypeScript Sandbox SDK volumes","href":"/sandboxes/sdk/typescript/volumes"},{"title":"TypeScript Sandbox SDK reference","href":"/sandboxes/sdk/typescript/reference"},{"title":"TypeScript Sandbox SDK errors","href":"/sandboxes/sdk/typescript/errors"}]}]},{"title":"Sandboxes CLI","href":"/sandboxes/cli"}]},{"group":"Add-ons","pages":[{"title":"Add-ons","sidebarTitle":"Overview","description":"Extend your Porter cluster with managed Postgres and Redis databases, persistent storage, third-party monitoring tools, and custom Helm charts","href":"/addons/overview"},{"group":"QM","root":{"title":"QM","sidebarTitle":"QM","href":"/addons/qm"},"pages":[{"title":"QM Use Cases","sidebarTitle":"Use cases","description":"[QM](/addons/qm) gives your team AI teammates that share context, files, and schedules. At Porter, we run QM on our own infrastructure, allowing our agents to securely deploy internal apps and connect to existing data. This page outlines three ways we use QM internally:","href":"/addons/qm/use-cases"}]},{"title":"Datastores","sidebarTitle":"Datastores","href":"/addons/datastores"},{"title":"Custom Helm Charts","sidebarTitle":"Custom Helm Charts","href":"/addons/custom-helm-charts"},{"title":"Third-party observability","sidebarTitle":"Third-party Observability","href":"/addons/third-party-observability"},{"title":"Persistent storage","sidebarTitle":"Storage","href":"/addons/storage"}]},{"group":"Integrations","pages":[{"title":"Doppler","href":"/integrations/doppler"},{"title":"Infisical","href":"/integrations/infisical"}]},{"group":"Command Line Interface (CLI)","pages":[{"title":"CLI installation","href":"/cli/installation"},{"title":"CLI basic usage","href":"/cli/basic-usage"},{"group":"Command Reference","pages":[{"title":"porter apply","sidebarTitle":"porter apply","href":"/standard/cli/command-reference/porter-apply"},{"title":"porter auth","href":"/standard/cli/command-reference/porter-auth"},{"title":"porter clusters","href":"/standard/cli/command-reference/porter-clusters"},{"title":"porter cloud-accounts","href":"/standard/cli/command-reference/porter-cloud-accounts"},{"title":"porter config","href":"/standard/cli/command-reference/porter-config"},{"title":"porter project","href":"/standard/cli/command-reference/porter-project"},{"title":"porter app","href":"/standard/cli/command-reference/porter-app"},{"title":"porter job","href":"/standard/cli/command-reference/porter-job"},{"title":"porter datastore connect","href":"/standard/cli/command-reference/porter-datastore-connect"},{"title":"porter env","href":"/standard/cli/command-reference/porter-env"},{"title":"porter registry","href":"/standard/cli/command-reference/porter-registry"},{"title":"porter target","href":"/standard/cli/command-reference/porter-target"}]}]},{"group":"Preview Environments","pages":[{"title":"Preview environments","href":"/preview-environments/overview"},{"title":"Preview environment overrides","href":"/preview-environments/defining-overrides"},{"title":"Custom preview environment workflows","href":"/preview-environments/custom-workflows"}]},{"group":"Security and Compliance","pages":[{"title":"Role-based access control","href":"/security-and-compliance/role-based-access-control"},{"title":"Audit logs","href":"/security-and-compliance/audit-logs"},{"title":"AWS permissions required for Porter","sidebarTitle":"AWS Roles \u0026 Permissions","href":"/security-and-compliance/aws-permissions"},{"title":"Static egress IPs","href":"/security-and-compliance/static-egress-ip"},{"title":"Configuring AWS ALB on Porter","sidebarTitle":"Configuring AWS ALB","href":"/security-and-compliance/configuring-alb"},{"title":"Using Cloudflare DNS with Porter","sidebarTitle":"Cloudflare DNS","href":"/security-and-compliance/cloudflare-dns"},{"title":"SOC 2 and HIPAA compliance","href":"/security-and-compliance/soc2-hipaa"},{"title":"Porter IP ranges","sidebarTitle":"Porter's IP Ranges","href":"/security-and-compliance/porter-ip-ranges"},{"title":"Tailscale VPN","sidebarTitle":"Tailscale VPN","href":"/security-and-compliance/tailscale"}]},{"group":"Other Guides","pages":[{"title":"Running GPU workloads","href":"/other/gpu-workloads"},{"title":"Deleting dangling resources","href":"/other/deleting-dangling-resources"},{"title":"AWS VPC peering","sidebarTitle":"AWS VPC Peering","href":"/other/aws-vpc-peering"},{"title":"Kubernetes 101","href":"/other/kubernetes-101"},{"title":"Migrate from Heroku","href":"/other/migrate-from-heroku"}]},{"group":"Terms \u0026 Privacy","pages":[{"title":"Terms of service","href":"/terms-and-privacy/terms-of-service"},{"title":"Privacy policy","href":"/terms-and-privacy/privacy-policy"}]}]}]},"basePath":"","title":"QM - Porter","buildId":"dpl_DqYu5MPb49gWBBbUGgpHQUmCHY3P","deploymentId":"dpl_DqYu5MPb49gWBBbUGgpHQUmCHY3P","dynamics":["a74be98ba7"],"chunks":["zoom-image-lightbox-DTFzvaXJ.js","lightbox-handle-r17bVUz3.js"]}