Files
portainer-core/docs/AUTOMATION.md
T

7.8 KiB

Stack Automation Guide

Overview

The update-stack.sh script enables programmatic stack updates via Portainer's REST API. This allows LLM agents (like Claude) and automation scripts to safely update Portainer stacks without requiring UI access.

Quick Start

# Navigate to stacks directory
cd /home/jpmschweitzer/Projects/portainer-core/stacks

# Update a stack (interactive mode - first time)
./update-stack.sh open-webui.yml

# Subsequent updates (uses stored token)
./update-stack.sh open-webui.yml

How It Works

Authentication Flow

  1. First Run:

    • Prompts for Portainer username/password
    • Authenticates with Portainer API
    • Generates JWT access token
    • Saves token to .portainer-token (gitignored)
  2. Subsequent Runs:

    • Reads token from .portainer-token
    • Uses token for API calls
    • No credential prompts needed

Update Process

  1. Reads YAML file from stacks/ directory
  2. Authenticates with Portainer (or uses cached token)
  3. Looks up stack by name (filename without .yml)
  4. Sends updated stack configuration via API
  5. Portainer validates and applies changes

Usage Modes

Interactive Mode (Human Operators)

./update-stack.sh open-webui.yml

First run prompts for:

  • Portainer username
  • Portainer password

Token persists for subsequent runs.

Non-Interactive Mode (Automation/LLM Agents)

export PORTAINER_USERNAME="admin"
export PORTAINER_PASSWORD="your-secure-password"
./update-stack.sh open-webui.yml

Use this mode for:

  • CI/CD pipelines
  • LLM agent workflows
  • Automated deployment scripts
  • Cron jobs

Environment Variables

Variable Required Default Description
PORTAINER_URL No http://localhost:8080 Portainer instance URL
PORTAINER_USERNAME Non-interactive only - Admin username
PORTAINER_PASSWORD Non-interactive only - Admin password

Examples

Update Single Stack

./update-stack.sh open-webui.yml

Update Multiple Stacks

for stack in open-webui.yml ollama.yml core-api.yml; do
    ./update-stack.sh "$stack"
    echo "---"
done

LLM Agent Integration

# Claude Code workflow example
export PORTAINER_USERNAME="admin"
export PORTAINER_PASSWORD="${PORTAINER_ADMIN_PASSWORD}"  # from secure env

# Update stack after modifying YAML
./update-stack.sh open-webui.yml

# Check result
echo $?  # 0 = success, 1 = failure

Remote Portainer Instance

export PORTAINER_URL="https://portainer.example.com"
./update-stack.sh my-stack.yml

Security Considerations

Token Storage

  • Token stored in .portainer-token (gitignored)
  • File permissions: 600 (owner read/write only)
  • Token expires based on Portainer settings (default: 8 hours)
  • Re-authentication automatic if token expires

Credentials

DO NOT:

  • Commit .portainer-token to git
  • Hardcode passwords in scripts
  • Share tokens between users
  • Use root/admin account for automation (create dedicated API user)

DO:

  • Use environment variables for non-interactive mode
  • Store credentials in secure password manager
  • Create dedicated Portainer user for automation
  • Rotate passwords regularly
  • Use .gitignore to exclude token file

Best Practices

  1. Create Automation User:

    Portainer → Users → Add User
    Username: portainer-automation
    Role: Environment Administrator (or custom)
    
  2. Use Environment Variables:

    # In ~/.bashrc or secure environment
    export PORTAINER_USERNAME="portainer-automation"
    export PORTAINER_PASSWORD="$(pass show portainer/automation)"  # from password manager
    
  3. Restrict Permissions:

    • Grant minimum required permissions
    • Limit to specific environments/stacks if possible

Troubleshooting

Authentication Failed

[ERROR] Failed to authenticate. Check credentials and try again.

Solutions:

  • Verify username/password are correct
  • Check Portainer is accessible: curl http://localhost:8080/api/status
  • Ensure user has admin/environment admin role
  • Try removing .portainer-token and re-authenticating

Stack Not Found

[ERROR] Stack 'my-stack' not found in Portainer
Available stacks:
  - open-webui
  - ollama
  - core-api

Solutions:

  • Verify stack name matches filename (without .yml)
  • Check stack exists in Portainer UI
  • Stack name is case-sensitive
  • Create stack in Portainer first if it doesn't exist

Connection Refused

[ERROR] Failed to connect to Portainer at http://localhost:8080

Solutions:

  • Check Portainer is running: docker ps | grep portainer
  • Verify port: Portainer default is 8080
  • Set PORTAINER_URL if using different port/host
  • Check firewall rules if accessing remote instance

Token Expired

[ERROR] Invalid authentication token

Solutions:

  • Delete token file: rm .portainer-token
  • Re-run script to re-authenticate
  • Check Portainer token expiration settings

YAML Validation Error

[ERROR] Stack update failed: invalid compose file

Solutions:

  • Validate YAML syntax: yamllint open-webui.yml
  • Check Docker Compose version compatibility
  • Review Portainer logs: docker logs portainer
  • Test with docker compose config -f open-webui.yml

Integration with LLM Agents

Claude Code Workflow

This script is designed to integrate seamlessly with Claude Code workflows:

  1. Agent modifies YAML file:

    # Claude uses Edit tool to update open-webui.yml
    
  2. Agent calls update script:

    cd /home/jpmschweitzer/Projects/portainer-core/stacks
    ./update-stack.sh open-webui.yml
    
  3. Agent verifies deployment:

    docker logs open-webui --tail 20
    curl http://localhost:82  # Verify service
    

Setting Up for Claude

Add to user profile or environment:

# In ~/.bashrc or secure location
export PORTAINER_USERNAME="admin"
export PORTAINER_PASSWORD="your-secure-password"

# Or use password manager
export PORTAINER_PASSWORD="$(pass show portainer/admin)"

Then Claude can directly call:

./update-stack.sh <stack-file.yml>

Advanced Usage

Custom Portainer URL

# Connect to remote Portainer
export PORTAINER_URL="https://portainer.mydomain.com"
./update-stack.sh open-webui.yml

Token Management

# View current token (for debugging)
cat .portainer-token | base64 -d | jq

# Force re-authentication
rm .portainer-token
./update-stack.sh open-webui.yml

# Use specific token
echo "your-jwt-token-here" > .portainer-token
chmod 600 .portainer-token

Dry Run (Check Only)

# Validate YAML before updating
docker compose -f open-webui.yml config

# Check current stack status
curl -s http://localhost:8080/api/stacks \
  -H "Authorization: Bearer $(cat .portainer-token)" | jq

API Reference

The script uses these Portainer API endpoints:

  • POST /api/auth - Authenticate and get token
  • GET /api/endpoints - List Docker endpoints
  • GET /api/stacks - List all stacks
  • PUT /api/stacks/{id} - Update specific stack

For full API documentation: https://docs.portainer.io/api/docs

Maintenance

Regular Tasks

  • Monthly: Rotate automation user password
  • Quarterly: Review and audit API access logs
  • After incidents: Revoke and regenerate tokens

Token Rotation

# Revoke old token (Portainer UI)
Portainer → Users → [user] → Access Tokens → Revoke All

# Re-authenticate
rm .portainer-token
./update-stack.sh open-webui.yml

Support

For issues or questions:

  1. Check Portainer logs: docker logs portainer
  2. Review this guide's Troubleshooting section
  3. Check Portainer API docs: https://docs.portainer.io/api/docs
  4. Open issue in project repository

Last updated: 2025-11-14