Skip to content

Deploy or Update a Portainer Swarm Stack or Standalone Docker Compose Stack Easily from GitHub Actions with Custom Options

License

Notifications You must be signed in to change notification settings

cssnr/portainer-stack-deploy-action

Use this GitHub action with your project
Add this Action to an existing workflow or create a new one
View on Marketplace

Repository files navigation

GitHub Tag Major GitHub Tag Minor GitHub Release Version GitHub Dist Size Workflow Release Workflow Test Workflow Lint Quality Gate Status GitHub Last Commit Codeberg Last Commit Docs Last Commit GitHub Contributors GitHub Repo Size GitHub Top Language GitHub Discussions GitHub Forks GitHub Repo Stars GitHub Org Stars Discord Ko-fi

Portainer Stack Deploy Action

Portainer Stack Deploy

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

▶️ View the Getting Started Guide on the website.

- 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.

Features

  • 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.

Inputs

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.

name

Swarm sack name or Compose project name.

Example: cool-stack

url

Portainer URL.

This is the base url to your Portainer instance.

Example: https://portainer.example.com:9443

token

Portainer API token.

For Instructions to create an API token visit: https://docs.portainer.io/api/access

file

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

endpoint

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]}

ref

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 }}

repo

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 }}

tlsskip

Skips SSL verification when cloning the Git repository. Set to true to enable.

Default: false

prune

Prune services that are no longer referenced (only available for Swarm stacks). Set to false to disable.

Default: true

pull

Pull latest image before deploy. Set to false to disable.

Default: true

type

Type of Deployment. Supports either repo or file.

Default: repo

standalone

Deploy a compose stack instead of swarm. Set to true to enable.

Default: false

env_data

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: value2

Warning

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.

env_file

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: .env
KEY="Value"
KEY_2="Value 2"

Note: Additional inputs are excluded for brevity.

merge_env

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/password

Username for private repository authentication when type is set to repo.

This is NOT your Portainer username, see token for Portainer authentication.

fs_path

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.

headers

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) }}

summary

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
ItemValue
ID112
Nametest_portainer-stack-deploy
Filedocker-compose.yml
TypeSwarm
StatusActive
Created2/28/2025, 3:09:16 AM
Updated-
Path/data/compose/112
EndpointID1
SwarmIDwr8i8agdr05n6wsf1tkcnhwik

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

Outputs

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 }}'"

Examples

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: file
Deploy 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.yaml
Deploy 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/master
Specify 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: .env
Merging 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: true
Multiline 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.yaml
Deploy 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: /mnt
Full 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.com

For more examples, you can check out other projects using this action:
https://github.com/cssnr/portainer-stack-deploy-action/network/dependents

Troubleshooting

  • 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: write

Permissions documentation for Workflows and Actions.

Tags

The following rolling tags are maintained.

Version Tag Rolling Bugs Feat. Name Target Example
GitHub Tag Major Major vN.x.x vN
GitHub Tag Minor Minor vN.N.x vN.N
GitHub Release 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.

Support

For general help or to request a feature, see:

If you are experiencing an issue/bug or getting unexpected results, you can:

For more information, see the CSSNR SUPPORT.md.

Contributing

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.

Ko-fi

Additionally, you can support other GitHub Actions I have published:

❔ Unpublished Actions

These actions are not published on the Marketplace, but may be useful.


📝 Template Actions

These are basic action templates that I use for creating new actions.

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/

About

Deploy or Update a Portainer Swarm Stack or Standalone Docker Compose Stack Easily from GitHub Actions with Custom Options

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Sponsor this project

Contributors 3

  •  
  •  
  •