Skip to main content
Glama

Docker MCP

PowerShell syntax Static analysis CodeQL License

A deliberately restricted Model Context Protocol (MCP) server for inspecting and maintaining a local Docker Desktop environment without giving the MCP client the raw Docker socket or a general host shell.

It is intended for an AI-assisted local infrastructure stack where the model should be able to answer questions such as:

  • Which containers are running?

  • Which image or Compose deployment is stale?

  • Does this container run as root?

  • Is a newer image available?

  • Which known repository checkout is behind its remote?

  • Can this one allowlisted service be safely restarted or redeployed?

The answer should not require handing the model arbitrary Docker administrator access.

Independent project. This repository is not a fork of docker/hub-mcp or docker/mcp-gateway. It can integrate with Docker MCP Toolkit/Gateway, but the restricted local maintenance layer in this repository is its own implementation.

Why this project exists

Mounting /var/run/docker.sock into a model-facing service is effectively equivalent to granting broad Docker administrator capability. From there, generic container creation or host mounts can become host compromise.

Docker MCP inserts explicit policy boundaries between the MCP client and that socket.

MCP client
   |
   v
Docker MCP server
   |
   v
restricted Docker API proxy
   |
   v
Docker Engine

Only the restricted socket proxy receives the Docker socket. The model-facing MCP container does not.

Windows host operations that cannot safely happen inside the container use a second, separate boundary:

MCP client
   |
   v
Docker MCP
   |
   v
host Maintenance Runner
   |
   +--> exact allowlisted repository alias
   +--> exact allowlisted Scheduled Task alias
   +--> exact allowlisted Compose project/service

The caller supplies an alias or a bounded operation — not an arbitrary path, branch, remote, command line or PowerShell expression.

Related MCP server: Docker MCP Server

What it can do

The tool surface has three main jobs.

Area

Examples

Inspection

containers, logs, stats, images, Compose state, deployment drift

Narrow maintenance

fast-forward an allowlisted repo, restart an allowlisted task/container, redeploy an allowlisted Compose target, clean known stale resources

Docker Scout

CVEs, recommendations, SBOM and image comparisons

The repository also includes a small host-side MCP port registry so local services can reserve stable ports instead of independently guessing from the same range.

For runtimes that genuinely require host-side recovery after Docker restarts — for example because credentials live only in tmpfs — an optional event-driven runtime supervisor can invoke preconfigured local recovery scripts. The supervisor itself is not exposed as a generic MCP command surface.

What it deliberately does not expose

The following are intentionally outside the MCP contract:

  • arbitrary docker exec.

  • arbitrary docker run.

  • a general shell or PowerShell runner.

  • raw Docker socket access.

  • arbitrary host filesystem paths.

  • arbitrary Git remotes or branches.

  • arbitrary Compose files/build contexts.

  • generic container deletion.

  • arbitrary Scheduled Task names.

  • arbitrary process arguments.

This is the core design constraint, not an accidental limitation.

Security model

The maintained deployment uses several layers rather than trusting one gateway:

  • the MCP server runs non-root.

  • the model-facing container never receives the raw Docker socket.

  • the restricted proxy exposes only the Docker API operations required by the reviewed tools;

  • container_inspect deliberately omits environment variables and command lines while still exposing fields needed for privilege audits;

  • repository maintenance is fail-closed: exact local root, clean checkout, expected HTTPS GitHub origin, main branch and fast-forward-only update;

  • host maintenance resolves aliases from ignored local configuration.

  • public access, when enabled, is authenticated through Cloudflare Access and a reviewed gateway;

  • machine paths, identities, allowlists, Access configuration and credentials remain outside Git.

Local and remote architecture

Local use:

local MCP client
      |
      v
Docker MCP Toolkit / profile
      |
      v
Docker MCP server
      |
      v
restricted socket proxy
      |
      v
Docker Desktop Engine

Optional remote use:

ChatGPT / remote MCP client
      |
      v
Cloudflare Access
      |
      v
authenticated MCP gateway
      |
      v
Docker MCP server
      |
      v
restricted socket proxy
      |
      v
Docker Desktop Engine

The public edge does not replace the local policy boundary; it adds authentication in front of it.

Quick start

Requirements:

  • Windows with Docker Desktop.

  • Docker MCP Toolkit.

  • PowerShell.

  • Docker Engine running.

Install the local profile from the repository root:

Set-ExecutionPolicy -Scope Process Bypass
.\install.ps1
.\test.ps1

The default profile is dockerlocal.

Run the profile locally with Docker MCP Gateway:

docker mcp gateway run --profile dockerlocal

The detailed sections below document the exact tool surface, optional Cloudflare path, host maintenance runner, port registry and runtime supervisor.

Scope

Read-only inspection:

  • containers_list

  • container_inspect — includes the configured container user for privilege audits without exposing environment variables or command lines

  • container_logs

  • container_stats

  • compose_status

  • images_list

  • image_inspect

  • mcp_deployment_audit — checks every running Compose project for Compose files newer than its containers, and additionally compares docker compose config --hash plus desired image for allowlisted projects. This catches both broad file-level drift and exact desired-config drift without a background watcher.

  • image_usage_audit — reports container usage plus repository-level superseded hints; hints are not deletion-safe because unused images can still be Dockerfile build-stage inputs

  • maintenance_job_status

  • maintenance_runner_status

  • port_registry_status — persistent host MCP port reservations plus listeners in the managed range that are not registered

Narrow maintenance:

  • repo_status — HEAD/branch/clean/conflict/origin eligibility for one local allowlisted repository alias

  • repo_pull_ff — exact git pull --ff-only origin main semantics for a clean allowlisted main checkout

  • scheduled_task_status — state for one allowlisted Windows Scheduled Task alias

  • scheduled_task_control — start/stop/restart one allowlisted Windows Scheduled Task alias

  • image_pull

  • container_restart

  • image_prune_dangling

  • cleanup_stale_mcp_containers

  • cleanup_superseded_images — deletes only explicitly supplied local image refs after verifying that no running or stopped container uses the resolved image; multiple aliases must be supplied explicitly and are untagged one by one without force

  • compose_redeploy

Host disk capacity (read-only)

host_disk_usage queries the existing allowlisted Windows maintenance runner for local fixed-volume capacity and available space. The result includes volume letters, total/available/used bytes, used percent and an observation timestamp. It takes no user-controlled path or command, does not inspect file names or credentials, and cannot clean up or resize disks. The MCP gateway and runtime socket-proxy retain their existing restrictions.

Treat measurement_status=unknown or a runner timeout as UNKNOWN, not 0% utilization. The host drive measurement is not the same as Docker's internal WSL2 virtual-disk usage. The daily watch alerts at roughly 80% used only when valid host capacity and available bytes were measured.

Docker Scout:

  • scout_quickview

  • scout_cves

  • scout_recommendations

  • scout_sbom

  • scout_compare

The project intentionally does not expose arbitrary docker exec, docker run, shell access, generic container deletion, arbitrary Compose paths, arbitrary builds, or the raw Docker socket.

Requirements

  • Windows with Docker Desktop

  • Docker MCP Toolkit

  • PowerShell

  • Docker Engine running

Local installation

Clone or download the repository, open PowerShell in the repository root, and run:

Set-ExecutionPolicy -Scope Process Bypass
.\install.ps1

The default Docker MCP profile is dockerlocal. To use another profile:

.\install.ps1 -Profile my-profile

The installer:

  1. builds the restricted Docker API proxy;

  2. verifies the proxy on loopback;

  3. builds the Docker MCP server image;

  4. installs the local Docker MCP catalog entry, adding the Maintenance Runner control directory bind mount (%LOCALAPPDATA%\DockerLocalMCP\control → /control) to the installed copy only;

  5. adds the server to the selected Docker MCP profile;

  6. enables the server tools and performs tool discovery.

Verify the installation:

.\test.ps1

To run the Docker MCP Gateway locally with this profile:

docker mcp gateway run --profile dockerlocal

For supported local MCP clients, Docker MCP Toolkit can also connect a client to the profile:

docker mcp client connect <client-name> --profile dockerlocal

The exact client names supported by Docker MCP Toolkit depend on the installed Docker Desktop version.

Because of the control-directory mount, every process that runs the gateway for this profile must set MCP_GATEWAY_DOCKER_BIND_ALLOW_WRITABLE_PATHS to that directory; otherwise Docker MCP Gateway refuses to start the server. See docs/CONNECTING.md.

Gateway-started containers get random Docker names. For stdio clients that should show up under an identifying name (dockerlocal-stdio-<client>-<pid>), use local-mcp/stdio-launcher.mjs instead; see docs/CONNECTING.md.

Remote connection through Cloudflare Access

The remote path is optional. It is intended for an MCP client that cannot directly reach the local Docker MCP profile.

Use placeholders such as:

Public hostname: docker-mcp.example.com
MCP endpoint:    https://docker-mcp.example.com/mcp
Access team:     team-name.cloudflareaccess.com
Origin target:   http://dockerlocal-gateway:8080

No real hostname, tunnel identifier, account identifier, email address, audience tag, token, or credential belongs in this repository.

1. Start the remote stack

Run:

Set-ExecutionPolicy -Scope Process Bypass
.\install-public.ps1

The installer prompts at runtime for:

  • Cloudflare Access team domain.

  • Access application audience tag.

  • allowed identity email.

Those values are written only to the ignored local file:

public/gateway.env

2. Create the Cloudflare side

In Cloudflare:

  1. create a Tunnel or use an existing Tunnel;

  2. create a public hostname such as docker-mcp.example.com;

  3. point the origin to http://dockerlocal-gateway:8080;

  4. protect the hostname with a Cloudflare Access self-hosted application;

  5. configure the Access policy for the identity that is allowed to use the MCP endpoint.

If cloudflared runs as a Docker container, attach it to the Docker MCP edge network:

docker network connect dockerlocal-public_edge <cloudflared-container>

The public MCP client should then connect to:

https://docker-mcp.example.com/mcp

The included gateway verifies the Cloudflare Access JWT, the configured audience, and the configured identity before forwarding MCP traffic.

See docs/CONNECTING.md for the full local and remote connection flow.

Host-side Compose maintenance runner

The Docker MCP container deliberately does not receive arbitrary host filesystem access. Narrow Compose redeploy operations are therefore delegated to a small Windows runner.

Install it with:

.\install-maintenance-runner.ps1

The runner accepts only:

  • an allowlisted project.

  • an allowlisted service for direct Compose projects.

  • redeploy_current.

  • rebuild_and_redeploy.

By default only this project's own Compose services are allowlisted.

Additional host projects can be configured locally in:

%LOCALAPPDATA%\DockerLocalMCP\maintenance-projects.local.json

Start from maintenance-projects.example.json. The local file is ignored by Git and should not contain secrets. The example includes an avanza-mcp-public entry; replace its placeholder with the local Avanza checkout path to allow safe gateway-only redeploys.

The allowlist is the source of truth for exact desired-Compose hash checks and write maintenance. Separately, the audit performs a read-only file-age check for every running Compose project using Docker's existing Compose labels and the host filesystem. This broad check does not grant redeploy rights. mcp_deployment_audit only performs these checks when called; there is no background reconciliation loop.

Two execution modes are supported:

  • Compose mode: the runner executes only the configured Compose files and allowlisted services.

  • Script-backed mode: the runner invokes one absolute, allowlisted PowerShell .ps1 wrapper with a fixed argument array for the requested operation. This is intended for projects whose wrapper owns required runtime setup such as DPAPI-backed or tmpfs secret injection.

Script-backed projects are whole-project operations: callers must omit the services argument. The local config contains the script path and fixed arguments only; credentials and secret values must remain in the project-specific secret store and must not be copied into the maintenance config. An operation may use an empty argument array when the allowlisted wrapper itself requires no parameters.

AIRSEA aggregate coverage: on-demand read-only access

airsea_coverage_report is a dedicated, no-argument, read-only inspection of the existing AIRSEA-Monitor shadow pilot. It returns up to 14 days of privacy-preserving AIS intake counts, acceptance/rejection reasons and coarse W/M/E-zone aggregates. This is not a Market Engine integration, recurring monitor, second collector, or trade signal. No raw vessel records, credentials, positions or host paths are accessible to the caller.

The existing Windows Maintenance Runner permits this function only when the specific MarketObservationPilotShadow scheduled-task alias is already allowlisted in local-only host maintenance configuration. Both the current DockerLocal MCP image and installed host-side runner must be updated before the tool appears. See AIRSEA read-only report design and release gate.

Allowlisted Windows host maintenance

The same host-side Maintenance Runner can expose a small Windows maintenance surface without exposing PowerShell or a generic process runner.

The MCP client supplies only an alias. Repository paths, the exact origin URL, the fixed main branch, Scheduled Task names and Task Scheduler paths are resolved from the local-only file:

%LOCALAPPDATA%\DockerLocalMCP\host-maintenance.local.json

Start from host-maintenance.example.json. The real file is covered by *.local.json in .gitignore and must not be committed.

Example shape:

{
  "repositories": {
    "example-repo": {
      "path": "<absolute-path-to-repository>",
      "origin_url": "https://github.com/example/example-repo.git",
      "branch": "main"
    }
  },
  "scheduled_tasks": {
    "example-service": {
      "task_name": "ExampleScheduledTask",
      "task_path": "\\"
    }
  }
}

Repository pulls fail closed unless all of these are true:

  • the configured path is the exact Git repository root.

  • the current branch is main.

  • tracked and untracked status is clean.

  • there are no unresolved index conflicts.

  • the current origin URL exactly matches the local allowlist.

  • the update can complete with --ff-only.

The runner also disables Git hooks and recursive submodule updates for maintenance Git calls, and disables interactive credential prompts. The MCP schema has no path, remote, branch, argument or command-string field.

Scheduled Task lookup is exact after local alias resolution. The remote MCP caller cannot choose a Task Scheduler name/path or PowerShell argument.

The four host-maintenance tools return compact structured JSON. If a bounded synchronous call times out, it returns a job_id that can be inspected with maintenance_job_status.

For the initial activation on an already-installed DockerLocal stack, enable-host-maintenance.ps1 validates one repository and one Scheduled Task, merges them into the local allowlist, preserves the existing Cloudflare gateway configuration, rebuilds the MCP image and restarts the Maintenance Runner. Re-run it with another pair of aliases to extend the local allowlist later.

.\enable-host-maintenance.ps1 `
  -RepositoryAlias example-repo `
  -RepositoryPath C:\path\to\example-repo `
  -ScheduledTaskAlias example-service `
  -ScheduledTaskName ExampleScheduledTask

To allowlist an additional repository without creating a Scheduled Task mapping, use the repo-only helper:

.\add-host-maintenance-repository.ps1 `
  -RepositoryAlias another-repo `
  -RepositoryPath C:\path\to\another-repo

The helper performs the same exact-root, main, and HTTPS GitHub-origin validation, preserves the existing task allowlist, and restarts only the local Maintenance Runner so it reloads the updated configuration.

Host MCP port registry

Local MCP services should not independently guess static ports. Docker MCP installs a small host-side registry at:

%LOCALAPPDATA%\DockerLocalMCP\port-registry.json

The default managed range is 8760–8799. A service reserves a port once through port-registry.ps1 and then reuses that reservation on every normal start. Allocation is protected by a named Windows mutex and an atomic JSON write.

The intended lifecycle is:

first install
  -> request preferred port (optional)
  -> skip ports reserved by another service
  -> skip ports with an unrelated live listener
  -> reserve first safe port
  -> persist mapping

normal start / restart
  -> read the existing service reservation
  -> use exactly that port
  -> if another process owns it, fail closed

Do not automatically move to another port at runtime. Cloudflare ingress, health checks and MCP clients rely on stable local endpoints. Dynamic allocation happens only when a service has no reservation yet.

For migration of an already-running service, the helper can adopt a listening port only when the installer supplies an expected command-line fragment and the listener owner matches it. This is for local installer use; the remote Docker MCP surface exposes only port_registry_status, never an arbitrary process/command matching primitive.

The allocator also inspects Docker's configured host-port bindings, including stopped containers. A Docker-backed service therefore keeps its host port protected even while its container is down; another MCP cannot claim the port during that outage.

The read-only status tool reports both Docker-configured host ports and live listeners inside 8760–8799 that are not yet in the registry. This makes legacy/manual allocations visible before they create another collision.

Existing non-Docker services can be seeded once with the local-only helper:

.\reserve-mcp-port.ps1 -Service example-http -PreferredPort 8765 -AdoptIfCommandContains "expected-process-fragment"

The command-line fragment is checked only on the host during migration and is not exposed through the remote MCP surface.

Event-driven local runtime supervisor

Some local MCP runtimes intentionally keep credentials only in ephemeral container storage such as tmpfs. Those credentials disappear when Docker Desktop or the container is restarted. Requiring a user to rerun a bootstrap script after every Docker restart defeats the purpose of a resilient local runtime.

Docker MCP therefore includes an optional host-side runtime supervisor. It is event-driven rather than a fixed polling watchdog:

Windows logon
  -> Runtime Supervisor
  -> Docker events
  -> relevant container start / restart / health change
  -> runtime health check
  -> allowlisted local recovery script

When Docker Engine itself disappears, the supervisor waits. When Docker becomes available again it subscribes to the Docker event stream before the initial reconciliation pass, so container events that occur during a longer recovery are buffered rather than lost. While idle it refreshes only its local heartbeat state; it does not query healthy containers every few minutes.

The supervisor contains no credentials and is not exposed as an MCP tool. It only invokes PowerShell scripts explicitly listed in the trusted local configuration file:

%LOCALAPPDATA%\DockerLocalMCP\runtime-supervisor.local.json

The configuration can define one or more container health checks per runtime: whether each named container is running, whether Docker reports it as healthy, and whether expected ephemeral files still exist inside it.

Recovery is restricted to an absolute local .ps1 file plus an argument array. There is no inline shell-command field. Recovery scripts are launched as isolated child PowerShell processes so Docker/Compose progress written to stderr cannot be mistaken for a supervisor failure. recovery_wait_seconds controls the bounded post-recovery health wait for runtimes that need a few seconds to become healthy.

Start with:

Copy-Item .\runtime-supervisor.example.json "$env:LOCALAPPDATA\DockerLocalMCP\runtime-supervisor.local.json"

Edit only the local copy to point at runtimes that genuinely need post-restart recovery, then install:

.\install-runtime-supervisor.ps1

The installer registers DockerLocalRuntimeSupervisor at Windows logon. By default it uses a tiny Windows Script Host wrapper so the long-running supervisor stays completely backgrounded even when Windows Terminal is the system's default console host. Pass -Visible only for foreground troubleshooting. The process remains attached to docker events; polling is used only while Docker Engine is unavailable so it can reconnect. Start/restart events use a short startup grace period for health-only runtimes, while tmpfs-file checks remain fast. Healthy healthcheck events are ignored so normal startup cannot create a recovery feedback loop.

Do not add a runtime merely because it is Dockerized. Persistent .env configuration and ordinary Docker restart policies do not need this supervisor. It is intended for runtimes with a real host-side recovery step, especially ephemeral secret rehydration.

Secret handling

Do not commit:

  • public/gateway.env.

  • .env files.

  • API keys, tokens, passwords, Access audience values, or identity allowlists.

  • maintenance-projects.local.json.

  • runtime logs, heartbeats, or maintenance job files.

Dependency and security automation

This repository includes:

  • Dependabot version updates for Python, Dockerfiles, Docker Compose and GitHub Actions.

  • CodeQL analysis for Python, JavaScript/TypeScript and GitHub Actions.

  • GitHub Dependabot vulnerability alerts through the repository's security settings.

License

MIT. See LICENSE.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    Not graded
    maintenance
    Enables AI assistants to interact with Docker containers through safe, permission-controlled access to inspect, manage, and diagnose containers, images, and compose services with built-in timeouts and AI-powered analysis.
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    Provides a local API to manage Docker containers and volumes, enabling operations like listing, inspecting, starting, stopping, and removing containers, as well as managing volumes, all through HTTP endpoints without shell commands.
    47 npm
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    A secure, local-first MCP server for read-only inspection and troubleshooting of development environments, exposing narrow, typed, auditable capabilities for repository inspection, log summarization, Docker review, and security scanning without granting unrestricted machine access.
    8
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables AI-assisted DevOps by exposing scoped, role-based tools to inspect and remediate a Docker app stack, with audit logging and allow-listed commands for safe incident response.
    -