From 10dfd606c07529bc0b8c0a1c3b47673f56ec7bb0 Mon Sep 17 00:00:00 2001 From: Adolfo Delorenzo Date: Mon, 21 Jul 2025 15:16:15 -0300 Subject: [PATCH] feat: streamline Docker deployment with registry images and unified env configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update docker-compose.yml to use pre-built images from GitLab registry - Replace individual environment variables with unified env_file directive - Create comprehensive .env.example with detailed instructions and troubleshooting - Add push-to-registry.sh script for building and pushing images to registry - Add docker-compose.prod.yml as reference for production deployments - Update documentation to reflect simplified deployment process Users can now deploy with just: cp .env.example .env docker-compose pull docker-compose up -d All 7 MCP server images are available at: git.oe74.net/adelorenzo/portainer-mcp/portainer-*:latest 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .env.example | 99 +++++++++++++++++++++++++++++++---- DOCKER.md | 34 ++++++++---- README.md | 8 +-- docker-compose.prod.yml | 111 ++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 105 +++++++++++++++---------------------- push-to-registry.sh | 70 +++++++++++++++++++++++++ 6 files changed, 340 insertions(+), 87 deletions(-) create mode 100644 docker-compose.prod.yml create mode 100755 push-to-registry.sh diff --git a/.env.example b/.env.example index 08a309c..eec6edc 100644 --- a/.env.example +++ b/.env.example @@ -1,18 +1,97 @@ -# Portainer MCP Environment Configuration -# Copy this file to .env and update with your values +# Portainer MCP Server Suite Environment Configuration +# ===================================================== +# This file contains all environment variables used by the Portainer MCP servers. +# Copy this file to .env and update with your actual values. +# +# IMPORTANT: Never commit the .env file to version control as it contains sensitive credentials. -# Required - Your Portainer instance URL +# REQUIRED CONFIGURATION +# ---------------------- + +# Your Portainer instance URL (must be accessible from Docker containers) +# Example: https://portainer.example.com +# Example: http://192.168.1.100:9000 PORTAINER_URL=https://your-portainer-instance.com -# Required - Your Portainer API key -# Generate from Portainer UI: My Account > Access Tokens -PORTAINER_API_KEY=your-api-key-here +# Your Portainer API key (also called Access Token) +# To generate an API key: +# 1. Log in to your Portainer instance +# 2. Click on your username in the top right +# 3. Go to "My account" → "Access tokens" +# 4. Click "Add access token" +# 5. Give it a name (e.g., "MCP Servers") +# 6. Copy the generated token and paste it here +PORTAINER_API_KEY=ptr_your-api-key-here -# Optional - Set to true if using self-signed certificates +# OPTIONAL CONFIGURATION +# ---------------------- + +# Set to true if using self-signed SSL certificates +# This disables SSL certificate verification (use with caution) +# Default: false PORTAINER_INSECURE=false -# Optional - HTTP request timeout in seconds +# HTTP request timeout in seconds +# Increase this value if you have slow network connections or large operations +# Default: 30 HTTP_TIMEOUT=30 -# Optional - Maximum number of retry attempts -MAX_RETRIES=3 \ No newline at end of file +# Maximum number of retry attempts for failed API calls +# The servers will retry with exponential backoff +# Default: 3 +MAX_RETRIES=3 + +# ADVANCED CONFIGURATION (rarely needed) +# -------------------------------------- + +# Log level for debugging (DEBUG, INFO, WARNING, ERROR) +# Default: INFO +# LOG_LEVEL=INFO + +# Enable debug mode for verbose output +# Default: false +# DEBUG=false + +# DOCKER COMPOSE SPECIFIC +# ----------------------- +# These variables are used by docker-compose.yml but not by the MCP servers + +# Docker registry to pull images from (if using private registry) +# Default uses the GitLab registry where images are hosted +# DOCKER_REGISTRY=git.oe74.net/adelorenzo/portainer-mcp + +# Image tag to use for all services +# Default: latest +# IMAGE_TAG=latest + +# NETWORK CONFIGURATION +# --------------------- + +# If your Portainer instance is running in Docker on the same host, +# you might need to use the Docker bridge network IP or container name +# Example: http://portainer:9000 (if Portainer container is named 'portainer') +# Example: http://172.17.0.1:9000 (Docker bridge IP on Linux) + +# TROUBLESHOOTING TIPS +# -------------------- +# +# 1. Connection refused errors: +# - Ensure PORTAINER_URL is accessible from within Docker containers +# - Try using the Docker host IP instead of localhost +# - Check if Portainer is running and accessible +# +# 2. Authentication errors: +# - Verify your API key is valid and not expired +# - Ensure the API key has the necessary permissions +# - Check if your Portainer user has the required role (Administrator/Operator) +# +# 3. SSL certificate errors: +# - Set PORTAINER_INSECURE=true for self-signed certificates +# - Ensure your Portainer instance has valid SSL certificates +# +# 4. Timeout errors: +# - Increase HTTP_TIMEOUT for slow connections +# - Check network connectivity between containers and Portainer +# +# 5. To test your configuration: +# docker-compose run --rm portainer-core python -c "import os; print('URL:', os.getenv('PORTAINER_URL'))" \ No newline at end of file diff --git a/DOCKER.md b/DOCKER.md index 1e24f16..edc1d9c 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -21,10 +21,15 @@ This guide covers how to build and run the Portainer MCP servers using Docker co ```bash cp .env.example .env # Edit .env with your Portainer credentials + nano .env # or use your preferred editor ``` -3. **Build all containers** +3. **Pull images (or build locally)** ```bash + # Option A: Pull from registry (recommended) + docker-compose pull + + # Option B: Build locally ./build-docker.sh all ``` @@ -92,19 +97,26 @@ Each MCP server runs in its own container with the following configuration: ## Environment Variables -All containers share the same environment variables: +All containers use a shared `.env` file for configuration. The docker-compose.yml file is configured to automatically load environment variables from this file using the `env_file` directive. -```bash -# Required -PORTAINER_URL=https://your-portainer-instance.com -PORTAINER_API_KEY=your-api-key-here - -# Optional -PORTAINER_INSECURE=false # Set to true for self-signed certificates -HTTP_TIMEOUT=30 -MAX_RETRIES=3 +```yaml +# docker-compose.yml snippet +services: + portainer-core: + image: git.oe74.net/adelorenzo/portainer-mcp/portainer-core:latest + env_file: + - .env ``` +See `.env.example` for a complete list of available environment variables with detailed documentation and troubleshooting tips. + +### Key Variables: +- `PORTAINER_URL` - Your Portainer instance URL (required) +- `PORTAINER_API_KEY` - Your Portainer API token (required) +- `PORTAINER_INSECURE` - Set to true for self-signed certificates +- `HTTP_TIMEOUT` - Request timeout in seconds +- `MAX_RETRIES` - Number of retry attempts + ## Docker Compose Configuration The `docker-compose.yml` file defines all services with: diff --git a/README.md b/README.md index 68b02ac..924cc7d 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ cd portainer-mcp # Docker deployment (recommended) cp .env.example .env # Edit .env with your Portainer URL and API key -./build-docker.sh all +docker-compose pull docker-compose up -d # Or Python local installation @@ -122,12 +122,14 @@ git clone https://github.com/yourusername/portainer-mcp.git cd portainer-mcp ``` -2. Build and run with Docker Compose: +2. Configure environment and run: ```bash cp .env.example .env # Edit .env with your Portainer credentials +nano .env # or use your preferred editor -./build-docker.sh all +# Pull pre-built images from registry +docker-compose pull docker-compose up -d ``` diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..6561f15 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,111 @@ +version: '3.8' + +services: + portainer-core: + image: git.oe74.net/adelorenzo/portainer-mcp/portainer-core:latest + container_name: portainer-mcp-core + ports: + - "3000:3000" + environment: + - PORTAINER_URL=${PORTAINER_URL} + - PORTAINER_API_KEY=${PORTAINER_API_KEY} + - PORTAINER_INSECURE=${PORTAINER_INSECURE:-false} + - HTTP_TIMEOUT=${HTTP_TIMEOUT:-30} + - MAX_RETRIES=${MAX_RETRIES:-3} + restart: unless-stopped + networks: + - portainer-mcp + + portainer-environments: + image: git.oe74.net/adelorenzo/portainer-mcp/portainer-environments:latest + container_name: portainer-mcp-environments + ports: + - "3001:3001" + environment: + - PORTAINER_URL=${PORTAINER_URL} + - PORTAINER_API_KEY=${PORTAINER_API_KEY} + - PORTAINER_INSECURE=${PORTAINER_INSECURE:-false} + - HTTP_TIMEOUT=${HTTP_TIMEOUT:-30} + - MAX_RETRIES=${MAX_RETRIES:-3} + restart: unless-stopped + networks: + - portainer-mcp + + portainer-docker: + image: git.oe74.net/adelorenzo/portainer-mcp/portainer-docker:latest + container_name: portainer-mcp-docker + ports: + - "3002:3002" + environment: + - PORTAINER_URL=${PORTAINER_URL} + - PORTAINER_API_KEY=${PORTAINER_API_KEY} + - PORTAINER_INSECURE=${PORTAINER_INSECURE:-false} + - HTTP_TIMEOUT=${HTTP_TIMEOUT:-30} + - MAX_RETRIES=${MAX_RETRIES:-3} + restart: unless-stopped + networks: + - portainer-mcp + + portainer-kubernetes: + image: git.oe74.net/adelorenzo/portainer-mcp/portainer-kubernetes:latest + container_name: portainer-mcp-kubernetes + ports: + - "3003:3003" + environment: + - PORTAINER_URL=${PORTAINER_URL} + - PORTAINER_API_KEY=${PORTAINER_API_KEY} + - PORTAINER_INSECURE=${PORTAINER_INSECURE:-false} + - HTTP_TIMEOUT=${HTTP_TIMEOUT:-30} + - MAX_RETRIES=${MAX_RETRIES:-3} + restart: unless-stopped + networks: + - portainer-mcp + + portainer-stacks: + image: git.oe74.net/adelorenzo/portainer-mcp/portainer-stacks:latest + container_name: portainer-mcp-stacks + ports: + - "3004:3004" + environment: + - PORTAINER_URL=${PORTAINER_URL} + - PORTAINER_API_KEY=${PORTAINER_API_KEY} + - PORTAINER_INSECURE=${PORTAINER_INSECURE:-false} + - HTTP_TIMEOUT=${HTTP_TIMEOUT:-30} + - MAX_RETRIES=${MAX_RETRIES:-3} + restart: unless-stopped + networks: + - portainer-mcp + + portainer-edge: + image: git.oe74.net/adelorenzo/portainer-mcp/portainer-edge:latest + container_name: portainer-mcp-edge + ports: + - "3005:3005" + environment: + - PORTAINER_URL=${PORTAINER_URL} + - PORTAINER_API_KEY=${PORTAINER_API_KEY} + - PORTAINER_INSECURE=${PORTAINER_INSECURE:-false} + - HTTP_TIMEOUT=${HTTP_TIMEOUT:-30} + - MAX_RETRIES=${MAX_RETRIES:-3} + restart: unless-stopped + networks: + - portainer-mcp + + portainer-gitops: + image: git.oe74.net/adelorenzo/portainer-mcp/portainer-gitops:latest + container_name: portainer-mcp-gitops + ports: + - "3006:3006" + environment: + - PORTAINER_URL=${PORTAINER_URL} + - PORTAINER_API_KEY=${PORTAINER_API_KEY} + - PORTAINER_INSECURE=${PORTAINER_INSECURE:-false} + - HTTP_TIMEOUT=${HTTP_TIMEOUT:-30} + - MAX_RETRIES=${MAX_RETRIES:-3} + restart: unless-stopped + networks: + - portainer-mcp + +networks: + portainer-mcp: + driver: bridge \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 083db0e..fc66b94 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,120 +2,99 @@ version: '3.8' services: portainer-core: - build: - context: . - dockerfile: docker/Dockerfile.core + image: git.oe74.net/adelorenzo/portainer-mcp/portainer-core:latest + # build: + # context: . + # dockerfile: docker/Dockerfile.core container_name: portainer-mcp-core ports: - "3000:3000" - environment: - - PORTAINER_URL=${PORTAINER_URL} - - PORTAINER_API_KEY=${PORTAINER_API_KEY} - - PORTAINER_INSECURE=${PORTAINER_INSECURE:-false} - - HTTP_TIMEOUT=${HTTP_TIMEOUT:-30} - - MAX_RETRIES=${MAX_RETRIES:-3} + env_file: + - .env restart: unless-stopped networks: - portainer-mcp portainer-environments: - build: - context: . - dockerfile: docker/Dockerfile.environments + image: git.oe74.net/adelorenzo/portainer-mcp/portainer-environments:latest + # build: + # context: . + # dockerfile: docker/Dockerfile.environments container_name: portainer-mcp-environments ports: - "3001:3001" - environment: - - PORTAINER_URL=${PORTAINER_URL} - - PORTAINER_API_KEY=${PORTAINER_API_KEY} - - PORTAINER_INSECURE=${PORTAINER_INSECURE:-false} - - HTTP_TIMEOUT=${HTTP_TIMEOUT:-30} - - MAX_RETRIES=${MAX_RETRIES:-3} + env_file: + - .env restart: unless-stopped networks: - portainer-mcp portainer-docker: - build: - context: . - dockerfile: docker/Dockerfile.docker + image: git.oe74.net/adelorenzo/portainer-mcp/portainer-docker:latest + # build: + # context: . + # dockerfile: docker/Dockerfile.docker container_name: portainer-mcp-docker ports: - "3002:3002" - environment: - - PORTAINER_URL=${PORTAINER_URL} - - PORTAINER_API_KEY=${PORTAINER_API_KEY} - - PORTAINER_INSECURE=${PORTAINER_INSECURE:-false} - - HTTP_TIMEOUT=${HTTP_TIMEOUT:-30} - - MAX_RETRIES=${MAX_RETRIES:-3} + env_file: + - .env restart: unless-stopped networks: - portainer-mcp portainer-kubernetes: - build: - context: . - dockerfile: docker/Dockerfile.kubernetes + image: git.oe74.net/adelorenzo/portainer-mcp/portainer-kubernetes:latest + # build: + # context: . + # dockerfile: docker/Dockerfile.kubernetes container_name: portainer-mcp-kubernetes ports: - "3003:3003" - environment: - - PORTAINER_URL=${PORTAINER_URL} - - PORTAINER_API_KEY=${PORTAINER_API_KEY} - - PORTAINER_INSECURE=${PORTAINER_INSECURE:-false} - - HTTP_TIMEOUT=${HTTP_TIMEOUT:-30} - - MAX_RETRIES=${MAX_RETRIES:-3} + env_file: + - .env restart: unless-stopped networks: - portainer-mcp portainer-stacks: - build: - context: . - dockerfile: docker/Dockerfile.stacks + image: git.oe74.net/adelorenzo/portainer-mcp/portainer-stacks:latest + # build: + # context: . + # dockerfile: docker/Dockerfile.stacks container_name: portainer-mcp-stacks ports: - "3004:3004" - environment: - - PORTAINER_URL=${PORTAINER_URL} - - PORTAINER_API_KEY=${PORTAINER_API_KEY} - - PORTAINER_INSECURE=${PORTAINER_INSECURE:-false} - - HTTP_TIMEOUT=${HTTP_TIMEOUT:-30} - - MAX_RETRIES=${MAX_RETRIES:-3} + env_file: + - .env restart: unless-stopped networks: - portainer-mcp portainer-edge: - build: - context: . - dockerfile: docker/Dockerfile.edge + image: git.oe74.net/adelorenzo/portainer-mcp/portainer-edge:latest + # build: + # context: . + # dockerfile: docker/Dockerfile.edge container_name: portainer-mcp-edge ports: - "3005:3005" - environment: - - PORTAINER_URL=${PORTAINER_URL} - - PORTAINER_API_KEY=${PORTAINER_API_KEY} - - PORTAINER_INSECURE=${PORTAINER_INSECURE:-false} - - HTTP_TIMEOUT=${HTTP_TIMEOUT:-30} - - MAX_RETRIES=${MAX_RETRIES:-3} + env_file: + - .env restart: unless-stopped networks: - portainer-mcp portainer-gitops: - build: - context: . - dockerfile: docker/Dockerfile.gitops + image: git.oe74.net/adelorenzo/portainer-mcp/portainer-gitops:latest + # build: + # context: . + # dockerfile: docker/Dockerfile.gitops container_name: portainer-mcp-gitops ports: - "3006:3006" - environment: - - PORTAINER_URL=${PORTAINER_URL} - - PORTAINER_API_KEY=${PORTAINER_API_KEY} - - PORTAINER_INSECURE=${PORTAINER_INSECURE:-false} - - HTTP_TIMEOUT=${HTTP_TIMEOUT:-30} - - MAX_RETRIES=${MAX_RETRIES:-3} + env_file: + - .env restart: unless-stopped networks: - portainer-mcp diff --git a/push-to-registry.sh b/push-to-registry.sh new file mode 100755 index 0000000..d8301dd --- /dev/null +++ b/push-to-registry.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +# Build and push Portainer MCP Docker images to GitLab container registry + +set -e + +# Configuration +REGISTRY="git.oe74.net/adelorenzo/portainer-mcp" +VERSION="${1:-latest}" + +# Colors for output +GREEN='\033[0;32m' +BLUE='\033[0;34m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +echo -e "${BLUE}Building and pushing Portainer MCP Docker images to GitLab registry${NC}" +echo -e "${BLUE}Registry: ${REGISTRY}${NC}" +echo -e "${BLUE}Version: ${VERSION}${NC}" +echo "" + +# Check if logged in to registry +echo -e "${YELLOW}Checking GitLab registry login...${NC}" +if ! docker login git.oe74.net 2>/dev/null; then + echo -e "${RED}Please login to GitLab registry first:${NC}" + echo "docker login git.oe74.net" + exit 1 +fi + +# Function to build and push a single image +build_and_push() { + local service=$1 + local image_name="${REGISTRY}/portainer-${service}" + + echo -e "${BLUE}Building portainer-${service}...${NC}" + docker build -f docker/Dockerfile.${service} -t ${image_name}:${VERSION} . + + if [ "${VERSION}" != "latest" ]; then + docker tag ${image_name}:${VERSION} ${image_name}:latest + fi + + echo -e "${BLUE}Pushing portainer-${service} to registry...${NC}" + docker push ${image_name}:${VERSION} + + if [ "${VERSION}" != "latest" ]; then + docker push ${image_name}:latest + fi + + echo -e "${GREEN}✓ portainer-${service} pushed successfully${NC}" + echo "" +} + +# Build and push all services +services=("core" "environments" "docker" "kubernetes" "stacks" "edge" "gitops") + +for service in "${services[@]}"; do + build_and_push "${service}" +done + +echo -e "${GREEN}All images built and pushed successfully!${NC}" +echo "" +echo "Images available at:" +for service in "${services[@]}"; do + echo " ${REGISTRY}/portainer-${service}:${VERSION}" +done +echo "" +echo "To use these images, update your docker-compose.yml:" +echo " image: ${REGISTRY}/portainer-core:${VERSION}" +echo " # instead of build: ..." \ No newline at end of file