# Container services Run personal software from a public container image on ClosedMCP. Choose a private browser interface for a web application, or `interface:"none"` for an always-running worker with no inbound endpoint. Use Serverless MCPs when publishing MCP tools from source code. A container service does not automatically become an MCP tool or receive a user's connected-account tokens. ## Create and open an app In the console, open **Container services**, choose or create a project, enter a service name, image version and HTTP port, then review and create. ClosedMCP resolves the image to an immutable digest. The app receives a stable private HTTPS URL. **Open app** signs the owner in on a separate app domain; copying its URL does not grant someone else access. The image must run as a non-root user (UID 1000 by default) and work with a read-only root filesystem. A web application must listen on `0.0.0.0` at the configured port. `/tmp` is temporary writable memory. The HTTP port can be 1024 through 65535. Web readiness requests `/` and expects HTTP 200–399; a worker has no HTTP probe, port declaration, ClusterIP application Service or inbound network grant. Worker `ready` confirms a running process, not completion of its application jobs. There is no public TCP, SSH, UDP, host networking or Docker socket. ## Agent workflow Start with `closedmcp_services` and check `runtime_enabled`. If false, explain that this installation cannot run containers yet. You may prepare or edit a configuration if the user wants, but do not request keys, promise startup, or keep polling for readiness. The platform operator must enable the runtime first. Plans and service status repeat this field. A true value means a runtime is configured; only the service's `ready` state confirms its current revision passed its runtime check. Find a project using `closedmcp_projects`, or create one with `closedmcp_create_project`. The signed-in human must own it and have deployment permission. Plan before applying: ```json { "project": "PROJECT_ID_FROM_THE_TOOL", "name": "my-web-app", "config": { "image": "docker.io/traefik/whoami:v1.11.0", "port": 8080, "environment": { "WHOAMI_PORT_NUMBER": "8080" } } } ``` Send that object to `closedmcp_service_plan`. Show the image, port, internet destinations, storage and restart impact to the user. Call `closedmcp_service_apply` with `{"plan":"RETURNED_PLAN_ID"}`. A plan lasts 15 minutes. Repeating apply with the same plan recovers the same operation; do not create a replacement service because a request timed out. Call `closedmcp_services` with `{"service":"RETURNED_SERVICE_ID"}` to read state. `ready` means the current revision passed its runtime check. `needs_setup` requires owner approval of keys or management scopes. Read `capabilities` for the actual inbound port (null for a worker), currently approved management scopes, writable roots, authenticated console link and next action. Open that console link and use **Open app** for browser access; it is not an MCP tool or a raw public endpoint. Poll pending status at a reasonable interval, such as five seconds. List services with `{}`; paginate with `after` set to the last returned ID. To run a worker, set `interface:"none"` and omit `port`. The image's original entrypoint is preserved unless `entry_command` supplies an explicit argv list; no shell is implicitly added. For example, a supported Python image can run `["python","worker.py"]` when that file is already in the image. An always-running worker must keep its main process alive. This mode does not publish MCP tools. Interactive exec/files management is a separate capability; never treat a denied MCP tool as permission to execute commands. ## Manage compute and files Request only the needed `config.management` scopes: `run`, `read_files` (also list) or `write_files`. Default is none. Even a worker with no keys waits for explicit owner approval in the authenticated setup console. A command can read the app's environment and approved keys. A file read can reveal data the image saved in temporary storage. Management never receives the platform's connected-account tokens or Kubernetes credentials. Use the existing `closedmcp_run`, `closedmcp_read_files`, `closedmcp_write_files` and `closedmcp_list_files` tools with `service` instead of `project`. Project calls retain their sandbox/Git behavior. For a container, run takes explicit `argv`, optional `cwd` (default `/tmp`) and `timeout_seconds` (1–60, default 30); there is no implicit shell. Files use absolute paths under `capabilities.writable_roots`. Run/write require a unique `idempotency_key` per intended operation. Reuse the same key after a lost reply: it cannot dispatch again within 24 hours, even when the outcome is unknown. Full replies are encrypted and kept for ten minutes or the last ten replies; later recovery returns the saved outcome only. At most one management operation runs per service and 1,000 new operations per day. Do not silently repeat a mutation with a new key. Inspect files using a new read operation, or stop the service to cancel its compute. Read/write support at most 16 files and 64 KiB total UTF-8 data; list returns at most 200 entries. Commands return at most 64 KiB output and report truncation, exit status, deadline and descendant cleanup. A multi-file write replaces each file atomically, but can return a partial result listing files already written. Management preserves the main container, its Pod identity and restart count. Image/settings/resource changes replace it and revoke prior management approval. Current membership, workspace, project ownership, deploy role and AI-app policy are checked on every call. An explicit `group` destination uses the existing group selection and app approval policy; a group credential cannot hop groups. Optional `resources` chooses `cpu_millicores` 100–1000, `memory_mib` 128–1024 in steps of 128 and `scratch_mib` 8–256 (at most half the memory). Defaults are 1000, 512 and 64. Memory is reserved, CPU has 100 millicores reserved with the configured burst limit, and memory-backed scratch counts toward the same memory limit. Changing these settings restarts the process and loses temporary files. ## Private key setup Declare environment **names**, for example `"keys":["GITHUB_TOKEN"]`, in the configuration. Call `closedmcp_service_setup_link` with the service ID and give its returned link to the user. The link opens the console and requires login as the original owner in the correct workspace. The owner reviews the exact image and enters the values there. Never ask for credentials in chat, put them in public environment settings, or pass them through an MCP tool. The link is single use, lasts 30 minutes and is bound to the service revision. An expired or stale link cannot change keys: request a new one. Reads reveal only whether each key is saved. Values are encrypted at rest and delivered only to the approved revision as environment variables. **The application can read those values.** ClosedMCP does not make an untrusted image safe to receive a sensitive credential; the owner must choose an appropriate credential and image. Image, listener and environment edits require fresh key approval if the service declares keys. Destination/port policy edits retain the existing approved process and key delivery. If a process holds approved keys or management permissions, widening its network access requires the owner to review and approve the exact destinations in the authenticated console; tightening applies immediately. A saved value can be retained by leaving its field blank. Removing a key from the configuration prevents delivery to later revisions. Updating keys restarts the app. Project transfers never transfer keys or browser access. ## Edit a port or image Open the service, change **HTTP port** or image, review, and apply. The URL remains the same. The app restarts and brief downtime is expected. Port configuration describes where the gateway connects; it does not reconfigure the application itself. If the application uses a setting such as `PORT`, change that public setting too. Agents first read the current configuration and `generation`, then plan with the same project and name, the complete new configuration and `expected_generation` set to that generation. A concurrent edit rejects the stale plan. Read status again before preparing a new plan. Use the old immutable image digest in a new plan to roll back an image; data migrations are not automatically undone. ## Internet access and storage Internet access is off by default. Set `egress` to exact public hostname and TCP port pairs, for example `[{"host":"api.github.com","port":443},{"host":"api.example.com","port":8443}]`. The older `https_hosts` field remains shorthand for TCP443; grants in both fields are combined. Clients must use HTTP CONNECT through the supplied authenticated proxy; common HTTPS clients honor `HTTPS_PROXY`. Direct sockets and DNS are blocked. Each DNS answer is checked and the checked address is connected; private, metadata and platform infrastructure addresses remain denied on every port. Redirects to another destination need a separate grant. Proxy-aware TCP clients can use an approved port; direct SSH and clients that bypass the proxy are unsupported. Use the same plan/apply flow to edit only outbound grants. The plan returns `restart_required:false`; applying advances `generation` while preserving `runtime_generation`, the process and its temporary files. New connections use the current policy immediately. Existing tunnels are rechecked every five seconds, with a one-second authorization timeout that fails closed, giving a six-second revocation bound under normal runtime scheduling. Image, listener, settings, key and lifecycle changes restart the process. Only callers acting with the current owner's account and deployment permission may prepare destination changes; the workload's proxy credential cannot change policy. When widening access for a process holding approved keys or management permissions, the plan returns `requires_network_approval:true` and `network_approval_url`. Return that link to the owner, wait for console approval, then read status. An MCP call cannot approve this widening. The owner reviews the exact image, current and proposed destination/port pairs and saved key names. Tightening needs no extra approval and preserves the process. Repeating the approved plan recovers its result. Native third-party credentials still have to be entered/configured for that application; a GitHub connection in ClosedMCP is not automatically an n8n credential. Optional `volume` has `path` and `size_gib` (1–10). Persistent storage starts only on an installation whose operator has qualified capacity enforcement and exclusive single-writer mounts. The default local-path provisioner is rejected. Without qualified storage the service is visibly `blocked`; temporary storage is never substituted. Existing volume paths and sizes cannot be edited. Stateful applications such as n8n require a separate image and storage qualification before use. ## Stop and delete `closedmcp_service_stop` takes a service ID and revokes browser and proxy access immediately; existing streams close within six seconds under normal runtime scheduling. The process then shuts down. Editing and applying its configuration restarts it, with fresh key approval when applicable. `closedmcp_service_delete` additionally requires `confirm_name` equal to the exact service name. It removes runtime access and key delivery. Persistent data is retained for operator recovery; self-service restore and purge are not implemented. A deleted name cannot be reused in the same project. Never report deployment or deletion complete until status confirms it. ## Current limits - One container, either one private HTTP interface or no inbound interface; one replica, no automatic scale-out. - Public Docker Hub and GHCR images only, with an explicit version or digest. No private registry credentials. - Ten active services per owner. Default 512 MiB memory/one-CPU limit, with bounded resource choices above; startup can wait for qualified worker capacity. - Owner-only browser access, sessions up to one hour, uploads up to 8 MiB. HTTP streaming and WebSockets are supported; web/service workers are disabled to prevent a previous image intercepting later sign-in. - No public webhooks, shared app access, raw application logs, private network access or automatic downstream-account delegation in this version. - Runtime availability is installation-specific. A saved plan does not prove that the installation has enabled or qualified deployment. For an already-built **MCP** image, use the project `closedmcp_publish` image option described in [Serverless MCPs](/docs/serverless#publish-an-already-built-image). It uses the MCP gateway, readiness, caller identity and app-version lifecycle. A generic Container service's web route does not register MCP tools, and image MCP publishing does not require creating one or exposing a raw public MCP port. The two capabilities are enabled and qualified separately. After an unknown management reply, retain the operation key and wait until its returned `busy_until` before a new inspection, or stop the service. A verified `not_started` result confirms no operation started; its key stays consumed, so a later intended attempt uses a new key.