Safeguard
AI Security•

How do you dockerize an MCP server written in JavaScript?

Package a Node MCP server in a container with a locked install, a non-root user and the right transport. Stdio needs an interactive run, HTTP needs auth.

Safeguard Security Team
4 min read

To dockerize a JavaScript MCP server, build a small Node image from a lockfile install, run as a non-root user, and pick the transport on purpose. Stdio servers need the container run interactively so the client can use standard input and output. HTTP servers need authentication, Origin checks and a bound interface.

Why put an MCP server in a container?

The MCP security guidance warns that local servers run with the user's permissions and may be able to reach their files and credentials. A container narrows that. The server sees only the folders you mount and the network you allow. If the package turns out to be malicious, the blast radius is the container, not the laptop.

How should the Dockerfile look?

The idea is a short recipe, not a template to paste. In words:

  1. Start from a pinned, maintained Node base image, referenced by digest where you can.
  2. Copy only package.json and the lockfile first, and run npm ci. This enforces the lockfile, as npm documents, and caches well.
  3. Use the ignore-scripts option for the install unless a dependency truly needs a script.
  4. Copy the source, and build if you use TypeScript.
  5. Create or use a non-root user, and switch to it before the start command.
  6. Set the start command to run the server entry file.

Docker's own security documentation explains why the user matters. By default containers start with a restricted set of Linux capabilities, and a process that merely needs to bind a port does not need to be root. Do not add capabilities you cannot justify.

Which transport are you using?

The MCP specification defines two standard transports. With stdio, the client launches the server as a subprocess and exchanges newline-delimited JSON-RPC over standard input and output. Logging may go to standard error. So a containerized stdio server is launched by the client with a command like docker run with the interactive flag, and it must write only protocol messages to standard output. A stray console.log to stdout breaks the protocol, so log to stderr.

With Streamable HTTP, the server is a service. The specification says servers must validate the Origin header to prevent DNS rebinding, should bind only to localhost when running locally, and should authenticate all connections. Inside a container, a server bound to all interfaces is reachable by whatever the port mapping exposes, so publish the port to 127.0.0.1 on the host unless you mean to share it.

What should you restrict at run time?

NIST SP 800-190 explains container security concerns and recommendations. Applied here:

  • Mount folders read-only unless the tool writes.
  • Pass secrets as short-lived values at start, not baked into the image.
  • Drop network access if the server does not need it, or limit it to the API it calls.
  • Set memory and CPU limits.
  • Do not mount the Docker socket into the container.

A constructed example

A team has a JavaScript MCP server that reads files from a project folder and calls an internal API. In the first version it runs directly with npx and can read the whole home directory. In the container version, only the project folder is mounted read-only, the API token is passed for that session, outbound traffic is limited to the API host, and the process runs as an unprivileged user. When a dependency update later proves to carry a bad postinstall script, the container never runs it, and even if the server code were hostile it would find no SSH keys.

How to check or apply this

  1. Decide the transport first.
  2. Build from a lockfile with npm ci, and pin the base image.
  3. Run as non-root, with read-only mounts where possible.
  4. Keep stdout clean for stdio servers, and send logs to stderr.
  5. For HTTP, require authentication, validate Origin and publish to localhost only.
  6. Scan the image and its dependencies before you publish it to a registry.
  7. Rebuild on a schedule to pick up base image fixes.

Safeguard's MCP Guardrail can add policy over which tools agents may call, and the dependency scan covers step 6.

Quick check

Why does my containerized MCP server hang in my client?

For stdio, the container usually needs to run in interactive mode so standard input stays open. Also check nothing else prints to standard output.

Is a container a sandbox?

It is isolation, not a guarantee. Combine it with a non-root user, limited mounts and network rules.

Sources

Last reviewed: 18 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.