344 lines
7.8 KiB
Markdown
344 lines
7.8 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
# 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)
|
|
|
|
```bash
|
|
./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)
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
./update-stack.sh open-webui.yml
|
|
```
|
|
|
|
### Update Multiple Stacks
|
|
|
|
```bash
|
|
for stack in open-webui.yml ollama.yml core-api.yml; do
|
|
./update-stack.sh "$stack"
|
|
echo "---"
|
|
done
|
|
```
|
|
|
|
### LLM Agent Integration
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
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:**
|
|
```bash
|
|
# 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:**
|
|
```python
|
|
# Claude uses Edit tool to update open-webui.yml
|
|
```
|
|
|
|
2. **Agent calls update script:**
|
|
```bash
|
|
cd /home/jpmschweitzer/Projects/portainer-core/stacks
|
|
./update-stack.sh open-webui.yml
|
|
```
|
|
|
|
3. **Agent verifies deployment:**
|
|
```bash
|
|
docker logs open-webui --tail 20
|
|
curl http://localhost:82 # Verify service
|
|
```
|
|
|
|
### Setting Up for Claude
|
|
|
|
Add to user profile or environment:
|
|
|
|
```bash
|
|
# 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:
|
|
```bash
|
|
./update-stack.sh <stack-file.yml>
|
|
```
|
|
|
|
## Advanced Usage
|
|
|
|
### Custom Portainer URL
|
|
|
|
```bash
|
|
# Connect to remote Portainer
|
|
export PORTAINER_URL="https://portainer.mydomain.com"
|
|
./update-stack.sh open-webui.yml
|
|
```
|
|
|
|
### Token Management
|
|
|
|
```bash
|
|
# 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)
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# 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*
|