Skip to content

Latest commit

 

History

History
164 lines (135 loc) · 9.26 KB

File metadata and controls

164 lines (135 loc) · 9.26 KB

Running the LocalStack MCP Server in Docker

The published image bundles everything the server shells out to — Terraform + tflocal, AWS CDK + cdklocal, AWS SAM + samlocal, and the Snowflake snow CLI — so the only dependency on your machine is Docker itself. LocalStack lifecycle, logs, and awslocal run through the Docker Engine API and LocalStack's REST APIs.

The image is multi-arch (linux/amd64 and linux/arm64).

How it works (Docker-out-of-Docker)

The container talks to your host Docker daemon through the bind-mounted /var/run/docker.sock. When you ask the server to start LocalStack, it creates a sibling localstack-main container on the host (not nested inside the MCP container) directly via the Docker API. Stop/restart operations act on the detected sibling container the same way. The MCP server and the IaC CLIs reach that sibling over the host gateway.

MCP client ── stdio ──► docker run … (MCP server)
                              │  /var/run/docker.sock (mounted)
                              ▼
                         host Docker daemon
                              └─ localstack-main  (sibling, publishes :4566 on the host)

Because LocalStack is a sibling container, one thing must be configured at run time:

  • Reachability — set LOCALSTACK_HOSTNAME=host.docker.internal so the server and the IaC CLIs target the sibling's published port instead of the container's own localhost.

LocalStack state lives in a named Docker volume (localstack-mcp) by default, so no host directory needs to be mounted or path-mirrored. To keep state in a host directory instead, set -e LOCALSTACK_VOLUME_DIR=/absolute/host/path (the path is interpreted by the host daemon).

Upgrading from an older image? Previous versions required a one-to-one cache mount plus XDG_CACHE_HOME. Old configs keep working: when XDG_CACHE_HOME is set, the server keeps using $XDG_CACHE_HOME/localstack/volume for state, so your persisted resources survive the upgrade. New configs need neither flag.

Quick start

docker run -i --rm \
  -v /var/run/docker.sock:/var/run/docker.sock \
  --add-host host.docker.internal:host-gateway \
  --add-host s3.host.docker.internal:host-gateway \
  --add-host snowflake.localhost.localstack.cloud:host-gateway \
  -e LOCALSTACK_AUTH_TOKEN="<YOUR_TOKEN>" \
  -e LOCALSTACK_HOSTNAME=host.docker.internal \
  localstack/localstack-mcp-server:latest
Flag Why it's needed
-v /var/run/docker.sock:/var/run/docker.sock Lets the server create/stop/restart the sibling LocalStack container, read its logs, and run awslocal inside it.
--add-host host.docker.internal:host-gateway Resolves host.docker.internal on Linux. Harmless on Docker Desktop (Mac/Windows), where it already resolves.
--add-host s3.host.docker.internal:host-gateway Lets CDK's virtual-hosted S3 endpoint resolve when cdklocal uses AWS_ENDPOINT_URL_S3=http://s3.host.docker.internal:4566.
--add-host snowflake.localhost.localstack.cloud:host-gateway Lets the Snowflake CLI reach the sibling Snowflake emulator through the hostname the emulator expects for routing.
-e LOCALSTACK_AUTH_TOKEN Required by every tool in this server.
-e LOCALSTACK_HOSTNAME=host.docker.internal Tells the server + IaC CLIs where the sibling LocalStack lives.

MCP client configuration

MCP clients launch the server over stdio. Note that client config files do not expand $HOME/$PWD — use absolute paths.

{
  "mcpServers": {
    "localstack-mcp-server": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "-v",
        "/var/run/docker.sock:/var/run/docker.sock",
        "--add-host",
        "host.docker.internal:host-gateway",
        "--add-host",
        "s3.host.docker.internal:host-gateway",
        "--add-host",
        "snowflake.localhost.localstack.cloud:host-gateway",
        "-e",
        "LOCALSTACK_AUTH_TOKEN",
        "-e",
        "LOCALSTACK_HOSTNAME=host.docker.internal",
        "-v",
        "/Users/you/projects:/Users/you/projects",
        "localstack/localstack-mcp-server:latest",
      ],
      "env": { "LOCALSTACK_AUTH_TOKEN": "<YOUR_TOKEN>" },
    },
  },
}

Deploying your IaC (mounting projects)

Deploys run inside the MCP container, so your project directory must be visible there. Mount it and pass the in-container path to the localstack-deployer tool. The simplest convention is to mount it at the same absolute path:

-v "/Users/you/projects/my-infra:/Users/you/projects/my-infra"

Then tell the tool directory: /Users/you/projects/my-infra.

Terraform, SAM, and CDK receive the LocalStack endpoint automatically when LOCALSTACK_HOSTNAME=host.docker.internal is set. CDK asset publishing is forced to path-style S3 inside the Docker image, so the single s3.host.docker.internal alias covers bootstrap asset uploads.

Known limitations

  • Extra host aliases. Include the aliases shown in the quick-start command.
  • First cold start of LocalStack can take up to ~2 minutes while the image is pulled and the runtime initializes; subsequent starts reuse the persisted volume.
  • Persistence across MCP restarts. The sibling localstack-main keeps running on the host even if your editor restarts the MCP container — reconnecting finds your stack still up. State persists in the localstack-mcp named volume (or LOCALSTACK_VOLUME_DIR if you set one).

Troubleshooting

Symptom Cause / fix
Tools report LocalStack Not Running after start Check LOCALSTACK_HOSTNAME=host.docker.internal is set and --add-host is present (Linux).
Auth Token Required LOCALSTACK_AUTH_TOKEN must be passed through (every tool requires it).
Docker Not Available / daemon unreachable Ensure /var/run/docker.sock is mounted (or pass DOCKER_HOST for a non-default daemon).
LocalStack container not found or Could not find a running LocalStack container named "localstack-main" Set MAIN_CONTAINER_NAME if you use a custom LocalStack container name.
State disappeared after upgrading the image Old configs stored state under $XDG_CACHE_HOME/localstack/volume — keep that env var, or point LOCALSTACK_VOLUME_DIR at the old directory.
MCP server containers pile up over time Older images did not exit when the client disconnected. Pull the latest image, then remove strays with docker ps -aq --filter ancestor=localstack/localstack-mcp-server | xargs docker rm -f.

Validating an image yourself

tests/docker/validate-image.mjs is a dependency-free MCP stdio client that drives the image through real tool calls.

LOCALSTACK_AUTH_TOKEN="<YOUR_TOKEN>" \
HARNESS_TOKEN_REAL=1 \
node tests/docker/validate-image.mjs -- \
  docker run -i --rm \
    -v /var/run/docker.sock:/var/run/docker.sock \
    --add-host host.docker.internal:host-gateway \
    --add-host s3.host.docker.internal:host-gateway \
    --add-host snowflake.localhost.localstack.cloud:host-gateway \
    -e LOCALSTACK_AUTH_TOKEN \
    -e LOCALSTACK_HOSTNAME=host.docker.internal \
    -v "$PWD/data:/work/data" \
    localstack/localstack-mcp-server:latest

Use HARNESS_SKIP to skip scenarios, for example:

HARNESS_SKIP=docs,cloudpods,ephemeral,replicator