Portainer
The portainer trigger updates Portainer-managed Docker Compose stacks through the Portainer stack redeploy API.
The portainer trigger lets Drydock apply tag updates to Portainer-managed Docker Compose stacks while keeping Portainer as the stack owner. It is a controller-local action: Portainer triggers are not registered in agent mode and cannot update containers discovered through an agent.
Portainer compatibility is intentionally narrow. The container must have non-empty com.docker.compose.project and com.docker.compose.service labels. Generic Docker containers, containers with only partial Compose labels, digest-only updates, stacks Portainer does not report as standalone Compose stacks, Drydock's own container, and containers marked dd.update.mode=infrastructure are rejected by the Portainer trigger. Configure a Docker action alongside Portainer when Drydock itself or infrastructure containers need the normal Docker self-update transition. Drydock pulls the image locally and runs the signature check and the vulnerability scan against the digest that pull fetched, then tells Portainer to redeploy with image pulling disabled so it deploys from the local image instead of resolving the tag again. Portainer performs the container create, not Drydock. Under a required post-pull identity binding policy (signature verification on, or availability policy other than warn), what Drydock hands Portainer is pinned to the bound repo:tag@sha256:... reference instead of the mutable tag, the same way DIGESTPINNING=true does for the Compose action, so the create is anchored to what the gate scanned rather than to whatever the tag resolves to at that moment. The pin follows the update mode: compose mode rewrites the service's image: line to the pinned reference, while env mode leaves the stack file alone and writes the digest into the version env variable as tag@sha256:..., so the rendered image is the pinned reference and the stack keeps its ${VAR} template. If no identity could be bound under required, the update is refused before Portainer is asked to do anything. Enable DIGESTPINNING explicitly to pin under an optional or disabled policy too; there a pin that cannot be expressed logs a warning and redeploys unpinned instead of failing the update. Left unpinned, the binding is checked after the fact rather than enforced at create time: if the tag moves in the meantime and Portainer creates a container whose image ID is not the scanned one, the redeploy never converges and that unscanned container keeps running until DD_ACTION_PORTAINER_{trigger_name}_REDEPLOYTIMEOUT expires, five minutes by default. Drydock then attempts to restore the previous stack, and a restore that fails as well is reported in the error rather than swallowed. Swarm and Kubernetes stacks are refused before the stack file is fetched and before anything is pulled, because their nodes pull the tag themselves and a scan on the controller says nothing about what the cluster runs. Portainer disables Drydock's automatic rollback monitor. Its normal lifecycle backup metadata is retained for history, but a Portainer-originated backup is rejected by the inherited Docker stop-remove-recreate rollback path; Docker-originated backups remain manually rollback-able when a Docker action is configured.
The trigger will:
- Resolve the Portainer stack from the container's Compose labels, using
dd.portainer.stack-idonly to disambiguate matching stacks - Fetch the current Portainer stack file and stack env
- Update either a stack env variable or the service image in the stack file
- Ask Portainer to redeploy the stack using the locally pulled and scanned image
- Run the normal Drydock update lifecycle gates around that redeploy
Variables
| Env var | Required | Description | Supported values | Default value when missing |
|---|---|---|---|---|
DD_ACTION_PORTAINER_{trigger_name}_URL | ✅ | Portainer server URL | HTTPS URL by default; HTTP only with ALLOWHTTP=true | |
DD_ACTION_PORTAINER_{trigger_name}_APIKEY | ✅ | Portainer API key | ||
DD_ACTION_PORTAINER_{trigger_name}_ALLOWHTTP | ⚪ | Explicitly permit plaintext HTTP for a private or isolated network | true, false | false |
DD_ACTION_PORTAINER_{trigger_name}_UPDATEMODE | ⚪ | How Drydock mutates the Portainer stack before redeploy | auto, env, compose | auto |
DD_ACTION_PORTAINER_{trigger_name}_VERSIONVARLABEL | ⚪ | Container label that names the Portainer stack env variable to update | Label name | dd.portainer.version-var |
DD_ACTION_PORTAINER_{trigger_name}_UPDATEMODELABEL | ⚪ | Container label that overrides the update mode for one container | Label name | dd.portainer.update-mode |
DD_ACTION_PORTAINER_{trigger_name}_PRUNESTACK | ⚪ | Ask Portainer to prune services that are no longer referenced by the stack | true, false | false |
DD_ACTION_PORTAINER_{trigger_name}_REDEPLOYTIMEOUT | ⚪ | Maximum time in milliseconds to wait for every running replica to converge after a Portainer redeploy | Positive integer | 300000 |
DD_ACTION_PORTAINER_{trigger_name}_PRUNE | ⚪ | If the old local Docker image must be pruned after upgrade | true, false | false |
DD_ACTION_PORTAINER_{trigger_name}_DRYRUN | ⚪ | Validate and preview the update without redeploying through Portainer | true, false | false |
DD_ACTION_PORTAINER_{trigger_name}_BACKUPCOUNT | ⚪ | Number of image backups to retain per container | Integer | 3 |
DD_ACTION_PORTAINER_{trigger_name}_PULLTIMEOUT | ⚪ | Maximum time allowed for an image pull and its progress stream (ms) | Positive integer | 600000 |
DD_ACTION_PORTAINER_{trigger_name}_DIGESTPINNING | ⚪ | Pin the redeploy to the bound repo:tag@sha256:... identity instead of the mutable tag: the service image: line in compose mode, the version env variable in env mode | true, false | false |
This trigger also supports the common configuration variables.
The env var keys for ALLOWHTTP, UPDATEMODE, VERSIONVARLABEL, UPDATEMODELABEL, PRUNESTACK, and DIGESTPINNING are case-insensitive. The camelCase forms are accepted too.
ALLOWHTTP=true permits the API key to cross the network without transport encryption. Use it only on a trusted, isolated network and prefer HTTPS or a TLS-terminating reverse proxy.Labels
| Label | Description | Supported values |
|---|---|---|
dd.action.include | Include this Portainer trigger for the container | Trigger id or name, e.g. portainer |
dd.action.auto | Allow automatic execution when the trigger uses AUTO=onauto | Trigger id or name |
dd.portainer.update-mode | Override the trigger-level update mode for one container | auto, env, compose |
dd.portainer.version-var | Portainer stack env variable to update in env mode | Env var name |
dd.portainer.stack-id | Explicit Portainer stack id when the stack cannot be inferred from Compose labels | Numeric stack id |
dd.portainer.endpoint-id | Explicit Portainer endpoint id override when a stack is available on more than one Portainer endpoint | Positive integer endpoint id |
Update Modes
auto is the default. Drydock inspects the service image in Portainer's stack file:
- If the image tag is declared as an env variable such as
pihole/pihole:${PIHOLE_TAG:-2026.05.0}, and that variable is referenced nowhere else in the Compose file, Drydock updates the Portainer stack env variable and leaves the compose file unchanged. - If the image tag is hardcoded such as
pihole/pihole:2026.05.0, Drydock patches only the target service'simagevalue in the Portainer stack file.
env updates a Portainer stack env variable only when the selected variable is referenced exclusively by the selected service's image tag. References in another service, another image, or any other Compose value fail closed. Set dd.portainer.version-var to name the variable when it is not detected, but the selected service image must still use that variable as its tag. The stack file is never rewritten in this mode, digest pinning included: a pinned redeploy sets the variable to tag@sha256:... rather than replacing the ${VAR} reference with a literal image, so the stack stays an env template and stays in env mode on the next cycle. Git-backed stacks, including stacks with GitOps metadata, aren't supported by the file-stack update path.
compose always patches the service image in the Portainer stack file. The patch uses the same service-scoped, formatting-preserving compose mutation helper as the Docker Compose trigger.
portainer action explicitly with dd.action.include=portainer so Drydock chooses this trigger instead of a generic Docker or Docker Compose action. Manual/API requests still pass through normal update-eligibility and action-policy checks. Automatic requests additionally require the trigger's AUTO policy and the global update mode to permit automatic updates. After Portainer accepts the redeploy, Drydock waits for the matching local Compose service to report the target tag; a timeout fails the request.dd.portainer.endpoint-id label selects the Portainer endpoint used for the stack update, while the container's local Compose labels select the service. Remote agent execution, self-update, infrastructure update, and digest-only update remain outside this trigger's scope. Automatic rollback is disabled for Portainer, and Portainer-originated backups are rejected by the backup API because applying them would inherit Docker stop-remove-recreate semantics. Configure a Docker action when manual rollback is required; that path remains available for Docker-originated backups. See Discussion #864 for the integration context.Example
services:
drydock:
image: codeswhat/drydock
environment:
- DD_ACTION_PORTAINER_LOCAL_URL=https://portainer.lan
- DD_ACTION_PORTAINER_LOCAL_APIKEY__FILE=/run/secrets/portainer_api_key
- DD_ACTION_PORTAINER_LOCAL_AUTO=oninclude
- DD_ACTION_PORTAINER_LOCAL_UPDATEMODE=autoFor a stack service using a Portainer env variable:
services:
pihole:
image: pihole/pihole:${PIHOLE_TAG:-2026.05.0}
labels:
dd.watch: "true"
dd.action.include: "portainer"
dd.portainer.version-var: "PIHOLE_TAG"When Drydock updates the container, Portainer's stack env receives the new PIHOLE_TAG, and Portainer redeploys the stack.