In this article
Cómo contenerizar servidores MCP en JavaScript
Los servidores MCP son extensiones que se conectan fácilmente a los LLM, pero a veces puede ser complicado configurarlos porque requieren un entorno de desarrollo. Para simplificar el proceso, vamos a proporcionar una imagen de Docker para nuestros servidores MCP.
En una publicación anterior, expliqué cómo ejecutar servidores MCP con Docker para que, como usuario que quiere conectarse a servidores MCP, no tengas que lidiar con instalar la versión correcta de Python ni averiguar cómo instalar npm o npx para los servidores MCP basados en JavaScript.
En esta publicación, nos enfocaremos en ti como desarrollador de servidores MCP y veremos lo sencillo que es ofrecer a quienes usan MCP una imagen de Docker. Configuraremos el entorno de desarrollo de MCP de la siguiente manera:
Configura un
Dockerfilepara tu servidor MCPCrea un flujo de trabajo de GitHub Actions que compile la imagen de Docker
Crea un flujo de trabajo de GitHub Actions que publique la imagen de Docker en GitHub Packages
Suponemos que el proyecto del servidor MCP está desarrollado en JavaScript (o TypeScript) y se basa en un entorno de desarrollo Node.js.
Como requisito previo, necesitarás un entorno de desarrollo Node.js, el proyecto MCP que quieres contenerizar y la aplicación Docker Desktop instalada localmente.
Configura un Dockerfile para tu servidor MCP
Un Dockerfile es la base de las imágenes de Docker. Define cómo se compila una imagen y cómo se ejecuta cuando se crea un contenedor.
Crea el siguiente Dockerfile:
FROM node:22-slim AS builder
WORKDIR /app
COPY . /app
RUN --mount=type=cache,target=/root/.npm npm install
RUN --mount=type=cache,target=/root/.npm npm run build
RUN --mount=type=cache,target=/root/.npm-production npm ci --ignore-scripts --omit-dev
FROM node:22-slim AS release
WORKDIR /app
COPY --from=builder /app/dist /app/dist
COPY --from=builder /app/package.json /app/package.json
COPY --from=builder /app/package-lock.json /app/package-lock.json
ENV NODE_ENV=production
RUN npm ci --ignore-scripts --omit-dev
ENTRYPOINT ["node", "/app/dist/bin/cli.cjs"]¿Qué sucede en este Dockerfile?
Este es un flujo de compilación de imágenes de Docker en varias etapas que sigue la práctica recomendada de separar la imagen que se usa para compilar el proyecto de la que se usa para crear un contenedor de Docker. Sigue las 10 prácticas recomendadas de Node.js para contenerizar aplicaciones Node.js con Docker.
Define un directorio
/app, donde estarán los archivos del proyecto del servidor MCP dentro del contenedorCuando se ejecute el contenedor de Docker, ejecutará el archivo
/app/dist/bin/cli.cjscomo argumento del entorno de ejecuciónnode. Probablemente quieras cambiarlo para que coincida con el punto de entrada de tu servidor MCP, comoindex.jsoserver.js.
¿Qué sigue? Ahora que tenemos un Dockerfile, podemos compilarlo y ejecutarlo localmente para asegurarnos de que funcione. Así se compila esta imagen de Docker (puedes reemplazar my-mcp-server por el nombre que quieras usar para etiquetar esta imagen de Docker):
docker build -t my-mcp-server .Ejecuta el comando anterior en el directorio raíz del proyecto, donde también debes colocar el Dockerfile. Se compilará una nueva imagen de Docker.
¿Cómo se ejecuta el servidor MCP desde una imagen de Docker? Usa este comando:
docker run -i --rm --init -e DOCKER_CONTAINER=true my-mcp-serverEstamos ejecutando un contenedor nuevo a partir de la imagen de Docker my-mcp-server que compilamos antes. Luego usamos los argumentos -i y --rm para indicarle a Docker que se trata de un proceso interactivo que se conecta a STDIO y que, cuando se detenga el contenedor, también lo elimine para dejar todo limpio.
¿Cómo usan Docker los usuarios para ejecutar un servidor MCP?
Ahora que tenemos un servidor MCP, ¿cómo pueden usarlo los clientes MCP y las aplicaciones host? ¿Cómo configuran el comando que se debe ejecutar si no es ux de Python, pip o npx de Node.js?
Necesitan tener Docker disponible y es probable que muchos desarrolladores ya tengan Docker o una alternativa compatible, como Podman. Una vez que tengan Docker, pueden configurar el servidor MCP de la siguiente manera:
{
"mcpServers": {
"my-mcp-server": {
"command": "docker",
"env": {},
"args": [
"run",
"-i",
"--rm",
"my-mcp-server"
]
}
}¡Casi! Este no es un ejemplo completo y no funcionará para los usuarios de MCP. Quizás notaste que hacemos referencia a my-mcp-server en el comando de Docker, pero ¿cómo sabe Docker de dónde obtener esta imagen? ¿Acaso la publicamos allí?
Eso es: falta este paso. Creamos un Dockerfile y compilamos una imagen de Docker localmente, pero no la publicamos en ningún lado. Para facilitar las cosas a quienes desarrollan software en GitHub, publicaremos la imagen en el registro de GitHub Packages, que puede alojar imágenes de Docker. De hecho, el servidor MCP oficial de GitHub también hace exactamente esto.
Crea un flujo de trabajo de GitHub Actions que compile la imagen de Docker y la publique en GitHub Packages
Empezaremos por crear un flujo de trabajo de GitHub Actions que genere una imagen de Docker y tenga los siguientes pasos en el flujo de trabajo de CI:
Compilar la imagen de Docker
Publicar la imagen de Docker en GitHub Packages
Firmar la imagen de Docker
A continuación, encontrarás el archivo completo del flujo de trabajo que debes crear en el siguiente directorio, en la raíz de tu proyecto: .github/workflows/docker-publish-to-github.yml.
Antes de ejecutar y guardar este archivo, modifica la variable de entorno IMAGE_NAME para que use el nombre de tu propia imagen de Docker en lugar del nombre de ejemplo que usamos, my-mcp-server:
name: "Docker: GitHub Packages"
on:
push:
branches:
- main
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository_owner }}/my-mcp-server
jobs:
build_and_publish:
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
id-token: write
attestations: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
type=ref,event=pr
flavor: |
prefix=
suffix=
- name: Build and push Docker image
id: push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v2
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: trueTambién notarás que el archivo de flujo de trabajo de GitHub Actions anterior, que compila la imagen de Docker, supone que la rama principal se llama main.
A continuación, tendrás que otorgar permisos de escritura a los ejecutores de GitHub Actions. Para hacerlo, ve a Settings -> Actions -> General en el repositorio y asegúrate de activar los permisos de lectura y escritura, como se muestra a continuación:

¿Quieres crear más con MCP?
¿Quieres saber qué hacer ahora? Reuní algunos recursos útiles que te ayudarán a aprender sobre los servidores MCP y a protegerlos:
Guía para principiantes: cómo entender visualmente la arquitectura MCP
Desde la inyección de prompts hasta otras vulnerabilidades de seguridad, Seguridad de MCP: lo viejo vuelve a ser nuevo
22 de octubre de 2025
DevSecCon2025 - La cumbre de seguridad de IA
Asegura tu lugar y protege la transición a la IA nativa