- Updated portainer_stacks_server.py to support GitOps configuration during stack creation - Added enable_gitops parameter and related GitOps settings to create_compose_stack_from_git - Created comprehensive documentation (README_GITOPS.md) explaining: - Why GitOps cannot be enabled on existing stacks - Why GitOps intervals cannot be updated without recreating stacks - API limitations that cause Git-based stacks to detach when updated - Added test script (test_gitops_create.py) to verify GitOps functionality - Included portainer_gitops_server.py for reference The Portainer API requires StackFileContent for updates, which detaches stacks from Git. This is a fundamental API limitation, not an MCP implementation issue. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
15 KiB
GitOps Implementation Fix for Portainer MCP Servers
Problem
The Portainer MCP servers were unable to enable GitOps on existing Git-based stacks. The enable_stack_gitops
function in portainer_gitops_server.py
was failing with "Invalid stack file content" errors.
Root Cause
The Portainer API's stack update endpoint (PUT /api/stacks/{stack_id}
) has limitations:
- It requires
StackFileContent
to be included in update requests - When
StackFileContent
is included, it converts Git-based stacks to file-based stacks - This sets
IsDetachedFromGit: true
and clears theGitConfig
Solution
Enable GitOps during stack creation rather than as a separate step. The API supports including autoUpdate
configuration when creating stacks from Git repositories.
Implementation Changes
Updated portainer_stacks_server.py
Added GitOps parameters to the create_compose_stack_from_git
tool:
# New parameters in the tool schema:
"enable_gitops": {
"type": "boolean",
"description": "Enable GitOps automatic updates",
"default": False
},
"gitops_interval": {
"type": "string",
"description": "GitOps polling interval (e.g., '5m', '1h')",
"default": "5m"
},
"gitops_mechanism": {
"type": "string",
"enum": ["polling", "webhook"],
"description": "GitOps update mechanism",
"default": "polling"
},
"gitops_pull_image": {
"type": "boolean",
"description": "Pull latest images on GitOps update",
"default": True
},
"gitops_force_update": {
"type": "boolean",
"description": "Force redeployment even if no changes",
"default": False
}
The implementation now adds autoUpdate
to the request when creating stacks:
if arguments.get("enable_gitops", False):
auto_update = {
"interval": arguments.get("gitops_interval", "5m"),
"forcePullImage": arguments.get("gitops_pull_image", True),
"forceUpdate": arguments.get("gitops_force_update", False)
}
if arguments.get("gitops_mechanism") == "webhook":
auto_update["webhook"] = arguments.get("gitops_webhook_id", "")
data["autoUpdate"] = auto_update
Usage
Creating a Stack with GitOps Enabled
# Using the MCP server
await tool.call("create_compose_stack_from_git", {
"environment_id": "6",
"name": "nginx-gitops",
"repository_url": "https://github.com/example/repo",
"repository_ref": "main",
"compose_path": "docker-compose.yml",
"enable_gitops": True,
"gitops_interval": "5m",
"gitops_mechanism": "polling",
"gitops_pull_image": True
})
Direct API Call
curl -X POST "https://portainer.example.com/api/stacks/create/standalone/repository?endpointId=6" \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"name": "nginx-gitops",
"repositoryURL": "https://github.com/example/repo",
"repositoryReferenceName": "main",
"composeFilePathInRepository": "docker-compose.yml",
"repositoryAuthentication": false,
"autoUpdate": {
"interval": "5m",
"forcePullImage": true,
"forceUpdate": false
}
}'
Alternative Approaches
If you need to enable GitOps on existing stacks:
- Delete and Recreate: Delete the existing stack and recreate it with GitOps enabled
- Use Webhooks: Configure webhooks at the Git repository level
- External Automation: Use external tools to trigger stack updates via the Git redeploy endpoint
Testing
Use the provided test_gitops_create.py
script to verify GitOps functionality:
python test_gitops_create.py
This will:
- Create a new stack with GitOps enabled
- Verify the GitOps configuration
- Display the stack details including AutoUpdate settings
Limitations
- Cannot enable GitOps on existing Git-based stacks without detaching them from Git
- Cannot update GitOps settings (like polling interval) on existing stacks without detaching them from Git
- The Portainer API does not provide a way to update Git-based stacks while maintaining Git connection
- Any update that includes
StackFileContent
converts the stack from Git-based to file-based - This appears to be a fundamental limitation in Portainer's API design
Why You Cannot Update GitOps Interval
When attempting to update the GitOps interval (e.g., from 5m to 3m) on an existing stack:
- API Limitation: The stack update endpoint (
PUT /api/stacks/{id}
) requiresStackFileContent
- Side Effect: Including
StackFileContent
in the update request:- Sets
IsDetachedFromGit: true
- Clears
GitConfig: null
- Clears
AutoUpdate: null
- Sets
- No Partial Updates: The API doesn't support updating only the
AutoUpdate
configuration
Workaround
To change the GitOps interval, you must:
- Delete the existing stack
- Recreate it with the new interval
Example:
# Delete existing stack
curl -X DELETE "https://portainer.example.com/api/stacks/{stack_id}?endpointId={env_id}" \
-H "X-API-Key: your-api-key"
# Recreate with new interval
curl -X POST "https://portainer.example.com/api/stacks/create/standalone/repository?endpointId={env_id}" \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"name": "stack-name",
"repositoryURL": "https://github.com/example/repo",
"repositoryReferenceName": "refs/heads/main",
"composeFilePathInRepository": "docker-compose.yml",
"autoUpdate": {
"interval": "3m",
"forcePullImage": true,
"forceUpdate": false
}
}'
Recommendations
- Always enable GitOps during stack creation if needed
- Consider opening a feature request with Portainer for enabling GitOps on existing stacks
- Use the Git redeploy endpoint for manual updates of Git-based stacks
API Field Reference
The complete autoUpdate
object structure:
{
"autoUpdate": {
"interval": "5m", // Polling interval (e.g., "1m30s", "5m", "1h")
"forcePullImage": true, // Pull latest images on update
"forceUpdate": false, // Force redeployment even if no changes
"webhook": "webhook-id", // Webhook ID (optional, for webhook mechanism)
"jobID": "15" // Job ID (managed by Portainer)
}
}
Original GitOps MCP Server Documentation
Features
- GitOps Configuration: Enable/disable automatic updates for stacks
- Update Mechanisms: Support for both webhook and polling-based updates
- Edge Stack Support: GitOps for edge computing environments
- Webhook Management: Generate and manage webhook URLs for CI/CD integration
- Git Credentials: Manage authentication for private repositories
- Manual Triggers: Trigger updates on-demand
- Update Windows: Configure deployment windows (Business Edition)
Installation
-
Ensure you have the Portainer MCP servers repository:
git clone https://github.com/yourusername/portainer-mcp.git cd portainer-mcp
-
Install dependencies:
pip install -r requirements.txt
-
Configure environment variables:
cp .env.example .env # Edit .env with your Portainer URL and API key
-
Make the server executable:
chmod +x portainer_gitops_server.py
Configuration
Add to your Claude Desktop configuration:
{
"portainer-gitops": {
"command": "python",
"args": ["/path/to/portainer-mcp/portainer_gitops_server.py"],
"env": {
"PORTAINER_URL": "https://your-portainer-instance.com",
"PORTAINER_API_KEY": "your-api-key"
}
}
}
Available Tools
Stack GitOps Management
list_gitops_stacks
List all stacks with GitOps configurations.
- Parameters:
environment_id
(optional): Filter by environment ID
get_stack_gitops_config
Get GitOps configuration for a specific stack.
- Parameters:
stack_id
(required): Stack ID
enable_stack_gitops
Enable GitOps automatic updates for a stack.
- Parameters:
stack_id
(required): Stack IDmechanism
(optional): Update mechanism - "webhook" or "polling" (default: "polling")interval
(optional): Polling interval like "5m", "1h" (default: "5m")force_update
(optional): Force redeployment even if no changes (default: false)pull_image
(optional): Pull latest images on update (default: true)
disable_stack_gitops
Disable GitOps automatic updates for a stack.
- Parameters:
stack_id
(required): Stack ID
trigger_stack_update
Manually trigger a GitOps update for a stack.
- Parameters:
stack_id
(required): Stack IDpull_image
(optional): Pull latest images (default: true)
Webhook Management
get_stack_webhook
Get webhook URL for a stack.
- Parameters:
stack_id
(required): Stack ID
regenerate_stack_webhook
Regenerate webhook URL for a stack.
- Parameters:
stack_id
(required): Stack ID
Edge Stack GitOps
list_gitops_edge_stacks
List all edge stacks with GitOps configurations.
enable_edge_stack_gitops
Enable GitOps for an edge stack.
- Parameters:
edge_stack_id
(required): Edge Stack IDmechanism
(optional): Update mechanism (default: "polling")interval
(optional): Polling interval (default: "5m")force_update
(optional): Force redeployment (default: false)
get_edge_stack_webhook
Get webhook URL for an edge stack.
- Parameters:
edge_stack_id
(required): Edge Stack ID
Git Credential Management
create_git_credential
Create Git credentials for private repositories.
- Parameters:
name
(required): Credential nameusername
(required): Git usernamepassword
(required): Git password or personal access token
list_git_credentials
List all Git credentials.
delete_git_credential
Delete a Git credential.
- Parameters:
credential_id
(required): Credential ID
GitOps Settings
get_gitops_settings
Get global GitOps settings.
update_gitops_settings
Update global GitOps settings.
- Parameters:
default_interval
(optional): Default polling intervalconcurrent_updates
(optional): Max concurrent updatesupdate_window
(optional): Update window configurationenabled
: Enable update windowstart_time
: Window start timeend_time
: Window end timetimezone
: Timezone for window
validate_git_repository
Validate access to a Git repository.
- Parameters:
repository_url
(required): Git repository URLreference
(optional): Branch or tag (default: "main")credential_id
(optional): Credential ID for private repos
Usage Examples
Enable GitOps with Polling
// Enable polling-based GitOps
await use_mcp_tool("portainer-gitops", "enable_stack_gitops", {
stack_id: "5",
mechanism: "polling",
interval: "10m",
force_update: false,
pull_image: true
});
// Check configuration
await use_mcp_tool("portainer-gitops", "get_stack_gitops_config", {
stack_id: "5"
});
Enable GitOps with Webhooks
// Enable webhook-based GitOps
await use_mcp_tool("portainer-gitops", "enable_stack_gitops", {
stack_id: "7",
mechanism: "webhook",
pull_image: true
});
// Get webhook URL
await use_mcp_tool("portainer-gitops", "get_stack_webhook", {
stack_id: "7"
});
Manage Git Credentials
// Create credential for private repository
await use_mcp_tool("portainer-gitops", "create_git_credential", {
name: "GitHub PAT",
username: "myusername",
password: "ghp_xxxxxxxxxxxx"
});
// List all credentials
await use_mcp_tool("portainer-gitops", "list_git_credentials", {});
Edge Stack GitOps
// Enable GitOps for edge stack
await use_mcp_tool("portainer-gitops", "enable_edge_stack_gitops", {
edge_stack_id: "3",
mechanism: "polling",
interval: "15m"
});
// List all GitOps edge stacks
await use_mcp_tool("portainer-gitops", "list_gitops_edge_stacks", {});
Manual Updates
// Trigger immediate update
await use_mcp_tool("portainer-gitops", "trigger_stack_update", {
stack_id: "5",
pull_image: true
});
GitOps Workflow
1. Setup Git Repository
- Store your Docker Compose or Kubernetes manifests in Git
- Use branches or tags for different environments
- Configure CI/CD pipelines to update manifests
2. Deploy Stack from Git
- Use the stacks server to create a stack from Git repository
- Provide credentials if using a private repository
3. Enable GitOps
- Choose between polling or webhook mechanism
- Configure update intervals for polling
- Set force update if you want Git as single source of truth
4. Webhook Integration
- Add webhook URL to your Git repository
- Configure to trigger on push events
- Use in GitHub Actions or other CI/CD tools
5. Monitor Updates
- Check GitOps status for stacks
- Review update logs in Portainer
- Use manual triggers for testing
Update Mechanisms
Polling
- Portainer periodically checks Git repository for changes
- Default interval: 5 minutes
- Suitable for: Regular updates, less critical deployments
- Pros: Simple setup, no external configuration
- Cons: Delayed updates, resource usage
Webhooks
- Git repository notifies Portainer of changes
- Immediate updates on push
- Suitable for: CI/CD pipelines, immediate deployments
- Pros: Instant updates, event-driven
- Cons: Requires webhook configuration in Git
Best Practices
- Use Webhooks for Production: Faster response to changes
- Set Appropriate Intervals: Balance between responsiveness and resource usage
- Enable Force Update Carefully: Can overwrite local changes
- Secure Credentials: Use personal access tokens, not passwords
- Test First: Use manual triggers to test deployments
- Monitor Logs: Check Portainer logs for update status
- Version Control: Use Git tags for production deployments
Security Considerations
- Git credentials are stored encrypted in Portainer
- Use personal access tokens with minimal permissions
- Webhook URLs contain unique IDs for security
- Enable HTTPS for webhook endpoints
- Regularly rotate Git credentials
- Use read-only tokens when possible
Troubleshooting
Common Issues
- Updates not triggering: Check Git credentials and repository access
- Webhook failures: Verify webhook URL and network connectivity
- Authentication errors: Ensure credentials have repository access
- Polling delays: Check interval settings and Portainer logs
Debug Mode
Enable debug logging by setting in your environment:
DEBUG=true
LOG_LEVEL=DEBUG
Requirements
- Python 3.8+
- Portainer Business Edition 2.19+ (for full GitOps features)
- Valid Portainer API token
- Git repository with Docker Compose or Kubernetes manifests
- Network access between Portainer and Git repository