Deploy, Update or Create a Portainer Stack from a Repository or Compose File. Supports both Swarm and Standalone Docker deployments for Portainer Community and Business Enterprise Edition. Includes most features including file or repo deploy, deploy from other repo, custom headers, and much more...
This action is written from the ground up in Vanilla JavaScript and is not a fork/clone of existing actions. You can view an Action Comparison of all available actions on the website.
Tip
- name: 'Portainer Deploy'
uses: cssnr/portainer-stack-deploy-action@v1
with:
name: stack-name
file: docker-compose.yaml
url: ${{ secrets.PORTAINER_URL }}
token: ${{ secrets.PORTAINER_TOKEN }}Make sure to review the Inputs and checkout additional Examples.
This is a fairly simple action, for more details see src/index.js and src/portainer.js.
No Portainer? You can deploy directly to Docker Swarm or Compose over SSH with: cssnr/stack-deploy-action or cssnr/docker-context-action.
- Deploy or re-deploy an existing stack otherwise create a new stack.
- Deploy from a repository or a compose file, see type.
- Deploy from a different repo than the current one.
- Provide environment variables in JSON/YAML or file format.
- Automatically parse Endpoint ID if only one endpoint.
- Supports Docker Swarm and Docker Standalone.
- Supports custom headers for services like Cloudflare Zero Trust.
- To view all features see the Inputs Documentation.
You can get started here or view workflow examples.
Important
Visit the Documentation Site for comprehensive, up-to-date documentation.
| Input | Default Value | Description of the Input |
|---|---|---|
| name | Required | Stack Name |
| url | Required | Portainer URL |
| token | Required | Portainer Token |
| file | docker-compose.yaml |
Compose File |
| endpoint | endpoints[0].Id |
Portainer Endpoint |
| ref | current reference |
Repository Ref |
| repo | current repository |
Repository URL |
| tlsskip | false |
Skip Repo TLS Verify |
| prune | true |
Prune Services |
| pull | true |
Pull Images |
| type | repo |
Type [repo, file] |
| standalone | false |
Deploy Standalone Stack |
| env_data | - | Env JSON/YAML Data |
| env_json | DEPRECATED | This has changed to env_data |
| env_file | - | Dotenv File Path |
| merge_env | false |
Merge Env Vars |
| username | - | Repository Username |
| password | - | Repository Password |
| fs_path | - | Relative Path (BE) |
| headers | - | Custom Headers JSON/YAML |
| summary | true |
Add Summary to Job |
For more details, see the Inputs Documentation and Portainer API Documentation.
Swarm sack name or Compose project name.
Example: cool-stack
Portainer URL.
This is the base url to your Portainer instance.
Example: https://portainer.example.com:9443
Portainer API token.
For Instructions to create an API token visit: https://docs.portainer.io/api/access
The Docker compose file. This path is relative to your working directory.
If you check out your repository to the root, and the compose file is called docker-compose.yaml, and is in the app directory, set file to: app/docker-compose.yaml
Default: docker-compose.yaml
If endpoint is not provided the first endpoint returned by the API will be used. If you only have one endpoint, this will work as expected, otherwise, you should provide an endpoint.
Example: 1
Default: ${endpoints[0]}
This defaults to the reference that triggered the workflow.
If deploying from a different repository than the current one, you may want to specify the ref of that repository to deploy from.
Example: refs/heads/master
Default: ${{ github.ref }}
This defaults to the repository running the action.
If you want to deploy a different repository, put the full http URL to that repository.
Example: https://github.com/cssnr/portainer-stack-deploy-action
Default: ${{ github.server_url }}/${{ github.repository }}
Skips SSL verification when cloning the Git repository.
Set to true to enable.
Default: false
Prune services that are no longer referenced (only available for Swarm stacks).
Set to false to disable.
Default: true
Pull latest image before deploy. Set to false to disable.
Default: true
Type of Deployment. Supports either repo or file.
Default: repo
Deploy a compose stack instead of swarm. Set to true to enable.
Default: false
Optional environment variables used when creating the stack.
These can be provided in JSON or YAML format and can be used with env_file. Values in env_file take precedence over these values.
👀 View Example JSON/YAML Data Format
These examples are identical, just different ways of passing the input.
data: |
{
"key1": "value1",
"key2": "value2"
}data: |
key1: value1
key2: value2Warning
Inputs are NOT secure unless using secrets or secure output.
Using env_data on a public repository will otherwise expose this data.
To securely pass an environment use the env_file option.
Environment File in dotenv format, parsed using dotenv.
This can be used with env_data. Values in this file take precedence over env_data.
👀 View Environment File Example
- uses: cssnr/portainer-stack-deploy-action@v1
with:
env_file: .envKEY="Value"
KEY_2="Value 2"Note: Additional inputs are excluded for brevity.
Set this to true to merge the current environment variables from the existing stack
with any newly provided variables in the env_data or env_file inputs.
When not providing the env_data or env_file inputs the current environment variables from the existing stack are always used.
When deploying a new stack, there are no current environment variables to merge, and this has no effect.
Default: false
Username for private repository authentication when type is set to repo.
This is NOT your Portainer username, see token for Portainer authentication.
Relative Path Support for Portainer BE. Set this to enable relative path volumes support for volume mappings in your compose file.
For more info see the Portainer Documentation - Relative Path Support.
Custom Headers in JSON or YAML format for services like Cloudflare Zero Trust.
The headers are parsed with JSON.parse or yaml.load and passed directly to axios.
👀 View Custom Headers Example
YAML
- uses: cssnr/portainer-stack-deploy-action@v1
with:
env_data: |
CF-Access-Client-Id: ${{ secrets.CF_CLIENT_ID }}
CF-Access-Client-Secret: ${{ secrets.CF_CLIENT_SECRET }}Multi-Line JSON
- uses: cssnr/portainer-stack-deploy-action@v1
with:
env_data: |
{
"CF-Access-Client-Id": "${{ secrets.CF_CLIENT_ID }}",
"CF-Access-Client-Secret": "${{ secrets.CF_CLIENT_SECRET }}"
}toJSON Output
- uses: cssnr/portainer-stack-deploy-action@v1
with:
env_data: ${{ toJSON(steps.import-secrets.outputs) }}Write a Summary for the job. To disable this set to false.
To view a workflow run, click on a recent Test job (requires login).
👀 View Example Job Summary
🎉 Created New Stack 112: test_portainer-stack-deploy
Stack Details
| Item | Value |
|---|---|
| ID | 112 |
| Name | test_portainer-stack-deploy |
| File | docker-compose.yml |
| Type | Swarm |
| Status | Active |
| Created | 2/28/2025, 3:09:16 AM |
| Updated | - |
| Path | /data/compose/112 |
| EndpointID | 1 |
| SwarmID | wr8i8agdr05n6wsf1tkcnhwik |
Tip
View the Inputs Documentation for more details.
- name: 'Portainer Deploy'
uses: cssnr/portainer-stack-deploy-action@v1
with:
token: ${{ secrets.PORTAINER_TOKEN }}
url: https://portainer.example.com:9443
name: stack-name
file: docker-compose.yaml| Output | Output Description |
|---|---|
| stackID | Resulting Stack ID |
| swarmID | Resulting Swarm ID |
| endpointID | Endpoint ID |
- name: 'Portainer Deploy'
id: stack
uses: cssnr/portainer-stack-deploy-action@v1
with:
token: ${{ secrets.PORTAINER_TOKEN }}
url: https://portainer.example.com:9443
name: stack-name
- name: 'Echo Output'
run: |
echo "stackID: '${{ steps.stack.outputs.stackID }}'"
echo "swarmID: '${{ steps.stack.outputs.swarmID }}'"
echo "endpointID: '${{ steps.stack.outputs.endpointID }}'"View more Examples on the website.
💡 Click on an example heading to expand or collapse the example.
Deploy from a compose file
- name: 'Portainer Deploy'
uses: cssnr/portainer-stack-deploy-action@v1
with:
token: ${{ secrets.PORTAINER_TOKEN }}
url: https://portainer.example.com:9443
name: stack-name
file: docker-compose.yaml
type: fileDeploy from the repository
- name: 'Portainer Deploy'
uses: cssnr/portainer-stack-deploy-action@v1
with:
token: ${{ secrets.PORTAINER_TOKEN }}
url: https://portainer.example.com:9443
name: stack-name
file: docker-compose.yamlDeploy from a different repository
- name: 'Portainer Deploy'
uses: cssnr/portainer-stack-deploy-action@v1
with:
token: ${{ secrets.PORTAINER_TOKEN }}
url: https://portainer.example.com:9443
name: stack-name
file: docker-compose.yaml
repo: https://github.com/user/some-other-repo
ref: refs/heads/masterSpecify environment variables
You can use env_data, env_file, or both.
- name: 'Portainer Deploy'
uses: cssnr/portainer-stack-deploy-action@v1
with:
token: ${{ secrets.PORTAINER_TOKEN }}
url: https://portainer.example.com:9443
name: stack-name
file: docker-compose.yaml
type: file
env_data: '{"KEY": "Value"}'
env_file: .envMerging existing environment variables
This will add the provided variables to the existing stack variables.
- name: 'Portainer Deploy'
uses: cssnr/portainer-stack-deploy-action@v1
with:
token: ${{ secrets.PORTAINER_TOKEN }}
url: https://portainer.example.com:9443
name: stack-name
file: docker-compose.yaml
type: file
env_data: |
KEY: Value
merge_env: trueMultiline JSON data input
Note: Secrets are secure in this context.
- name: 'Portainer Deploy'
uses: cssnr/portainer-stack-deploy-action@v1
with:
token: ${{ secrets.PORTAINER_TOKEN }}
url: https://portainer.example.com:9443
name: stack-name
file: docker-compose.yaml
type: file
env_data: |
{
"APP_PRIVATE_KEY": "${{ secrets.APP_PRIVATE_KEY }}",
"VERSION": "${{ inputs.VERSION }}"
}Only run on release events
This is accomplished by adding an if to the step.
if: ${{ github.event_name == 'release' }}
- name: 'Portainer Deploy'
uses: cssnr/portainer-stack-deploy-action@v1
if: ${{ github.event_name == 'release' }}
with:
token: ${{ secrets.PORTAINER_TOKEN }}
url: https://portainer.example.com:9443
name: stack-name
file: docker-compose.yamlDeploy with relative path volumes
Portainer Business Edition Only.
- name: 'Portainer Deploy'
uses: cssnr/portainer-stack-deploy-action@v1
with:
token: ${{ secrets.PORTAINER_TOKEN }}
url: https://portainer.example.com:9443
name: stack-name
file: docker-compose.yaml
fs_path: /mntFull build and deploy workflow
This example builds an image, pushes to a registry, then deploys to Portainer.
name: 'Portainer Stack Deploy'
on:
workflow_dispatch:
inputs:
tags:
description: 'Tags: comma,separated'
required: true
default: 'latest'
env:
REGISTRY: 'ghcr.io'
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: true
jobs:
build:
name: 'Build'
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
packages: write
steps:
- name: 'Checkout'
uses: actions/checkout@v5
- name: 'Setup Buildx'
uses: docker/setup-buildx-action@v3
with:
platforms: 'linux/amd64,linux/arm64'
- name: 'Docker Login'
uses: docker/login-action@v3
with:
registry: $${{ env.REGISTRY }}
username: ${{ secrets.GHCR_USER }}
password: ${{ secrets.GHCR_PASS }}
- name: 'Generate Tags'
id: tags
uses: cssnr/docker-tags-action@v1
with:
images: $${{ env.REGISTRY }}/${{ github.repository }}
tags: ${{ inputs.tags }}
- name: 'Build and Push'
uses: docker/build-push-action@v6
with:
context: .
platforms: 'linux/amd64,linux/arm64'
push: true
tags: ${{ steps.tags.outputs.tags }}
labels: ${{ steps.tags.outputs.labels }}
deploy:
name: 'Deploy'
runs-on: ubuntu-latest
timeout-minutes: 5
needs: build
steps:
- name: 'Checkout'
uses: actions/checkout@v5
- name: 'Portainer Deploy'
uses: cssnr/portainer-stack-deploy-action@v1
with:
token: ${{ secrets.PORTAINER_TOKEN }}
url: https://portainer.example.com
name: stack-name
file: docker-compose-swarm.yaml
cleanup:
name: 'Cleanup'
runs-on: ubuntu-latest
timeout-minutes: 5
needs: deploy
steps:
- name: 'Purge Cache'
uses: cssnr/cloudflare-purge-cache-action@v2
with:
token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
zones: cssnr.comFor more examples, you can check out other projects using this action:
https://github.com/cssnr/portainer-stack-deploy-action/network/dependents
- No such image: ghcr.io/user/repo-name:tag
Make sure your package is not private. If you intend to use a private package, then:
Go to Portainer Registries: https://portainer.example.com/#!/registries/new
Choose Custom registry, set ghcr.io for Registry URL, enable authentication, and add your username/token.
- Error: Resource not accessible by integration
Only applies to build-push-action or bake-action type actions, not this action.
Permissions can be added on the job or step level with:
permissions:
packages: writePermissions documentation for Workflows and Actions.
The following rolling tags are maintained.
| Version Tag | Rolling | Bugs | Feat. | Name | Target | Example |
|---|---|---|---|---|---|---|
| ✅ | ✅ | ✅ | Major | vN.x.x |
vN |
|
| ✅ | ✅ | ❌ | Minor | vN.N.x |
vN.N |
|
| ❌ | ❌ | ❌ | Micro | vN.N.N |
vN.N.N |
You can view the release notes for each version on the releases page.
The Major tag is recommended. It is the most up-to-date and always backwards compatible. Breaking changes would result in a Major version bump. At a minimum you should use a Minor tag.
For general help or to request a feature, see:
- Q&A Discussion: https://github.com/cssnr/portainer-stack-deploy-action/discussions/categories/q-a
- Request a Feature: https://github.com/cssnr/portainer-stack-deploy-action/discussions/categories/feature-requests
If you are experiencing an issue/bug or getting unexpected results, you can:
- Report an Issue: https://github.com/cssnr/portainer-stack-deploy-action/issues
- Chat with us on Discord: https://discord.gg/wXy6m2X8wY
- Provide General Feedback: https://cssnr.github.io/feedback/
For more information, see the CSSNR SUPPORT.md.
Contributions of all kinds are welcome, including updating this README.md. If you would like to submit a PR, please review the CONTRIBUTING.md.
To contribute to the documentation site go to cssnr/portainer-stack-deploy-docs.
Please consider making a donation to support the development of this project and additional open source projects.
Additionally, you can support other GitHub Actions I have published:
- Stack Deploy Action
- Portainer Stack Deploy Action
- Docker Context Action
- VirusTotal Action
- Mirror Repository Action
- Update Version Tags Action
- Docker Tags Action
- Update JSON Value Action
- JSON Key Value Check Action
- Parse Issue Form Action
- Cloudflare Purge Cache Action
- Mozilla Addon Update Action
- Package Changelog Action
- NPM Outdated Check Action
- Label Creator Action
- Algolia Crawler Action
- Upload Release Action
- Check Build Action
- Web Request Action
- Get Commit Action
❔ Unpublished Actions
These actions are not published on the Marketplace, but may be useful.
- cssnr/draft-release-action - Keep a draft release ready to publish.
- cssnr/env-json-action - Convert env file to json or vice versa.
- cssnr/push-artifacts-action - Sync files to a remote host with rsync.
- smashedr/update-release-notes-action - Update release notes.
- smashedr/combine-release-notes-action - Combine release notes.
📝 Template Actions
These are basic action templates that I use for creating new actions.
- js-test-action - JavaScript
- py-test-action - Python
- ts-test-action - TypeScript
- docker-test-action - Docker Image
Note: The docker-test-action builds, runs and pushes images to GitHub Container Registry.
For a full list of current projects visit: https://cssnr.github.io/