Skip to content

BTLzdravtech/portainer-stack-webhook

 
 

Repository files navigation

Portainer Stack Webhook

An equivalent solution to Portainer BE's Automatic Stack Updates feature, but free.

Just run the container, tell it how to access your portainer instance, and tada, it's done! 🎉

# docker-compose.yml
services:
  stack-webhook:
    image: aklinker1/portainer-stack-webhook
    ports:
      - 3000:3000
    environment:
      BASE_URL: https://portainer.example.com/api # Required, full URL including /api
      PORT: 3000                                  # Optional, default 3000
      POLL_INTERVAL_MS: 5000                      # Optional, status poll interval, default 5s
      POLL_TIMEOUT_MS: 1800000                    # Optional, give up after this long, default 30m (0 = no timeout)
      LOG_LEVEL: info                             # Optional, debug|info|warn|error|silent, default info
    stop_grace_period: 35m                        # Optional, let in-flight redeploys finish on restart

Authentication is per-request: every request must include an X-API-Key header containing a Portainer API access token. The token is forwarded to your Portainer instance, so the webhook performs whatever actions that token is allowed to.

To tell Portainer to pull the latest images and update the stack, make a simple POST request:

curl -X POST \
  -H "X-API-Key: your-portainer-api-token" \
  http://localhost:3000/api/webhook/stacks/:stackId

Portainer redeploys stacks asynchronously, so the webhook waits for the redeploy to finish before responding (polling the stack status every POLL_INTERVAL_MS, up to POLL_TIMEOUT_MS):

  • 200 with { "id", "name", "status" } once the stack is active again.
  • 502 if the redeploy fails.
  • 504 if it doesn't finish within POLL_TIMEOUT_MS.

Because pulling images can take many minutes (e.g. large or Windows images), the request can stay open for a long time. Make sure any client or reverse proxy in front of the webhook allows long-lived requests.

Note

The stackId can be retrieved from the URL when visiting the stack details in Portainer. In the URL below, it would be 22 from the id=22 query parameter.

https://portainer.example.com/#!/1/docker/stacks/some_stack?id=22&type=1&regular=true&external=false&orphaned=false

Logging

The service logs each request and the full redeploy lifecycle — trigger, update accepted, every status poll, and the final outcome — as colored, timestamped lines. Use LOG_LEVEL to control verbosity (debug adds per-request entry lines; warn hides the routine poll chatter). Colors are emitted only on a TTY and disabled when NO_COLOR is set, so aggregated logs stay plain text. The X-API-Key is never logged.

Production notes

  • Health check: GET /health returns 200 { "status": "ok", "version" } and requires no API key. The Docker image ships a built-in HEALTHCHECK that polls it.
  • Graceful shutdown: on SIGTERM/SIGINT the server stops accepting new connections and waits for in-flight redeploys to finish. Because a redeploy can run for many minutes, set the container's stop_grace_period (compose) / terminationGracePeriodSeconds (k8s) at least as high as POLL_TIMEOUT_MS, or in-flight deploys will be SIGKILLed on restart.

Contributing

To install dependencies:

bun install

To run:

  1. Copy the .env.template to .env and fill it out with your portainer instance's info:
    cp .env.template .env
  2. Start the server
    bun dev
  3. Send a request to test it out
    curl -X POST \
      -H "X-API-Key: your-portainer-api-token" \
      http://localhost:3000/api/webhook/stacks/123

You can also run tests:

bun test

About

Free alternative to Portainer BE's "Automatic Stack Updates"

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • TypeScript 98.3%
  • Dockerfile 1.7%