Deploying Portainer git stacks from the command line without bypassing Portainer
When you manage Docker stacks through Portainer's git integration, the temptation for CLI deploys is to SSH into the server and run `docker compose up -d --bui...
When you manage Docker stacks through Portainer's git integration, the temptation for CLI deploys is to SSH into the server and run docker compose up -d --build directly. Don't. That bypasses Portainer entirely — it won't know about your changes, env vars get out of sync, and you end up with a stack that looks fine in the terminal but wrong in the Portainer UI.
The better approach: use Portainer's own API. The /api/stacks/{id}/git/redeploy endpoint does exactly what the "Redeploy" button in the UI does — pulls the repo, rebuilds the image, updates env vars, restarts the container. Portainer stays the single source of truth.
Here's the core of a deploy script that runs locally and triggers Portainer remotely:
GIT_HASH=$(git rev-parse --short HEAD)
curl -s -X PUT "$PORTAINER_URL/api/stacks/$STACK_ID/git/redeploy?endpointId=$ENDPOINT_ID" \
-H "X-API-Key: $PORTAINER_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"env\": [
{\"name\": \"GIT_HASH\", \"value\": \"$GIT_HASH\"}
],
\"pullImage\": true,
\"repositoryAuthentication\": true,
\"repositoryUsername\": \"$GITHUB_USER\",
\"repositoryPassword\": \"$GITHUB_TOKEN\"
}"Key things I learned setting this up: private repos need repositoryAuthentication: true with a GitHub PAT (the UI handles this silently but the API won't). Env vars in the request body replace the full set, so include all your existing vars — not just the ones you're changing. And generate a dedicated Portainer API token under Settings → Access tokens rather than using your login credentials.
No files on the server outside Docker. No SSH. Portainer sees every deploy. Works from any machine with the API key.