plugins/arcane/skills/arcane-gitops/SKILL.md
Use this skill when the user asks about Arcane GitOps, deploying docker-compose stacks from a git repo, GitOps for self-hosted services, managing docker-compose stacks declaratively, or setting up automated deployments with Arcane and GitHub Actions. Also recall when the user asks about deploying cloudflared, Nextcloud, n8n, or any docker-compose stack via GitOps. For Cloudflare Tunnel setup details, also recall the cloudflare plugin's cloudflare-tunnels skill. For LXC host setup, recall the proxmox plugin's proxmox-lxc skill.
npx skillsauth add nsheaps/ai-mktpl arcane-gitopsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Arcane is a GitOps tool that syncs docker-compose stacks from a git repository to remote hosts. Push to main, and Arcane deploys. It provides a Portainer-like experience driven entirely by code in your repo.
Git Push → GitHub Actions → Arcane API → Host Agent → docker compose up
hosts/<hostname>/<stack>/docker compose up -d to apply changesiac/
├── hosts/
│ ├── README.md
│ ├── _example/ # Template for new stacks
│ └── <hostname>/ # One directory per host
│ ├── cloudflared/
│ │ └── docker-compose.yaml
│ ├── postgresql/
│ │ └── docker-compose.yaml
│ ├── redis/
│ │ └── docker-compose.yaml
│ ├── nextcloud/
│ │ └── docker-compose.yaml
│ └── n8n/
│ └── docker-compose.yaml
├── arcane/
│ └── hosts/
│ └── <hostname>/
│ └── arcane.json # Host-level Arcane config
├── apps/ # Reusable compose modules (optional)
│ └── README.md
└── .github/
└── workflows/
└── arcane-deploy.yaml # GitOps deployment workflow
hosts/<hostname>/ — match the actual hostnamehosts/<hostname>/<stack-name>/ — descriptive service name-compose.yaml or -compose.ymlarcane/hosts/<hostname>/arcane.jsonarcane.json){
"environment_id": "0"
}
The environment_id ties the host to an Arcane deployment environment.
Every stack that needs secrets follows the init-secrets pattern: a sidecar container fetches secrets from 1Password before the main service starts.
name: my-app
services:
init-secrets:
image: 1password/op:2
user: "0:0"
environment:
- OP_SERVICE_ACCOUNT_TOKEN=${OP_SERVICE_ACCOUNT_TOKEN}
volumes:
- app-secrets:/run/secrets:rw
command:
- /bin/bash
- -c
- |
op read "op://Infrastructure/my-app/db-password" > /run/secrets/db_password &
op read "op://Infrastructure/my-app/api-key" > /run/secrets/api_key &
wait
chmod 444 /run/secrets/*
my-app:
image: my-app:latest
restart: always
volumes:
- app-secrets:/run/secrets:ro
depends_on:
init-secrets:
condition: service_completed_successfully
networks:
- cloudflared
volumes:
app-secrets:
driver: local
networks:
cloudflared:
external: true
Key points:
OP_SERVICE_ACCOUNT_TOKEN is injected by Arcane as a global variable& + wait) for speed:rw for init, :ro for appchmod 444 makes secrets read-only after creationdepends_on with service_completed_successfully ensures secrets exist before app startsop://<vault>/<item>/<field>
# Examples:
op://Infrastructure/my-server--my-app/password
op://Infrastructure/my-server--postgres--root-user/username
op://Infrastructure/my-server--postgres--root-user/password
Convention: <host>--<service>--<component>/<field>
Stacks share named networks for inter-service communication:
# In the cloudflared stack (creates the network):
networks:
cloudflared:
driver: bridge
name: cloudflared
# In any stack that needs tunnel access (joins the network):
networks:
cloudflared:
external: true
Common shared networks:
cloudflared — services exposed via Cloudflare Tunnelpostgresql — services needing database accessredis — services needing cache/queue accessStacks that need a database include an init container:
services:
init-database:
image: postgres:17
depends_on:
init-secrets:
condition: service_completed_successfully
volumes:
- app-secrets:/run/secrets:ro
networks:
- postgresql
entrypoint: ["/bin/bash", "-c"]
command:
- |
export PGPASSWORD=$$(cat /run/secrets/root_password)
# Create user and database if they don't exist
psql -h postgresql -U postgres -tc "SELECT 1 FROM pg_roles WHERE rolname='myapp'" | grep -q 1 || \
psql -h postgresql -U postgres -c "CREATE USER myapp WITH PASSWORD '$$(cat /run/secrets/db_password)';"
psql -h postgresql -U postgres -tc "SELECT 1 FROM pg_database WHERE datname='myapp'" | grep -q 1 || \
psql -h postgresql -U postgres -c "CREATE DATABASE myapp OWNER myapp;"
The apps/ directory holds shared compose configurations included via the include directive:
# In a host-specific compose file:
include:
- ../../../apps/monitoring/docker-compose.yaml
Constraint: Included apps must have identical configuration across all hosts. No parameterization — if values differ, copy the compose file instead.
Pin images by digest for reproducibility:
services:
cloudflared:
image: cloudflare/cloudflared:2026.3.0@sha256:6b599ca3e974...
Use Renovate to auto-update pinned digests.
The arcane-deploy.yaml workflow has four stages:
arcane/hosts/ for changed projects using floating git tagsOP_SERVICE_ACCOUNT_TOKEN in Arcanedeployed/arcane/heapsnas/cloudflared)Uses floating git tags to track last deployment:
Tag: deployed/arcane/<host>/<project>
On each deploy, the tag moves to the current commit. Next run compares HEAD against the tag to detect changes. Force deploy (via workflow input) ignores tags and redeploys everything.
Create the directory:
mkdir -p hosts/<hostname>/<stack-name>
Add docker-compose.yaml following the patterns above
If secrets are needed, add them to 1Password under Infrastructure/<hostname>--<stack-name>
If the service needs external access, join the cloudflared network and configure the tunnel hostname in Cloudflare dashboard (or via Pulumi)
Commit and push to main:
git add hosts/<hostname>/<stack-name>/
git commit -m "feat(<hostname>): add <stack-name> stack"
git push
The GitHub Actions workflow deploys automatically
arcane-deploy workflow runsdocker compose -f /path/to/stack/docker-compose.yaml pstools
Manually reproduce what the github-app plugin's SessionStart hook does to make a GitHub App installation token usable in the current session — materialize the PEM, generate the token, isolate GH_CONFIG_DIR, write the runtime env file, and wire CLAUDE_ENV_FILE so every Bash call sees GH_TOKEN/GITHUB_TOKEN. Use when the hook did not run, the token is missing from the environment, or a shell/teammate needs the token wired up by hand. <example>GH_TOKEN isn't set even though github-app is configured</example> <example>the github-app SessionStart hook didn't run, set up the token manually</example> <example>wire the github app token into CLAUDE_ENV_FILE</example> <example>gh keeps falling back to the wrong account, isolate GH_CONFIG_DIR</example>
tools
Manually configure the GitHub App bot git identity the way the github-app plugin's SessionStart hook does — resolve the app slug and bot user ID, build the <slug>[bot] name and noreply email, set GIT_AUTHOR_*/GIT_COMMITTER_* env vars, and write an isolated GIT_CONFIG_GLOBAL with the gh auth git-credential helper. Use when commits are attributed to the wrong account, "Author identity unknown" appears, or git identity must be set up by hand. <example>my commits are showing up as the handler, not the bot</example> <example>git says Author identity unknown after the github-app hook ran</example> <example>configure the github app bot git identity manually</example> <example>set up the gh credential helper for git push</example>
tools
Manages spec files for requirements capture and validation
tools
# Bash Chaining Alternatives This skill teaches you how to work around the bash command chaining restriction enforced by this plugin. ## Why Chaining is Blocked The `bash-command-rejection` plugin blocks these operators: | Operator | Name | Why Blocked | | -------- | ---------- | ----------------------------------------------------------------------------------- | | `&&` | AND chain | Runs cmd2 only if cmd1 su