Skip to content

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 GitHub Top Language GitHub Forks GitHub Repo Stars GitHub Org Stars Discord

Portainer Stack Deploy Action

Deploy or Update a Portainer Stack from a Repository or Compose File. Supports most features including specifying the repository, compose file, environment variables and much more...

This action is written from the ground up in VanillaJS and is not a fork/clone of existing actions.

No Portainer? You can deploy directly to a docker over ssh with: cssnr/stack-deploy-action

Note

Please submit a Feature Request for new features or Open an Issue if you find any bugs.

This is a fairly simple action, for more details see src/index.js and src/portainer.js.

Inputs

Input Req. Default Value Input Description
token Yes - Portainer Token *
url Yes - Portainer URL
name Yes - Stack Name
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_json - - Dotenv JSON Data **
env_file - - Dotenv File Path *
merge_env - false Merge Env Vars *
username - - Repository Username *
password - - Repository Password *
fs_path - - Relative Path (BE) *
summary - true Add Summary to Job *

For more details on inputs, see the Portainer API documentation.

token: To create a Portainer API token see: https://docs.portainer.io/api/access

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.

ref: If you want to deploy a different ref than the one triggering the workflow. Useful if you are deploying from another repository. Example: refs/heads/master

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

type: Type of Deployment. Currently, supports either repo or file.

env_json/env_file: Optional environment variables used when creating the stack. File should be in dotenv format and JSON should be an object. Example: {"KEY": "Value"}

Warning

Inputs are NOT secure unless using secrets or secure output. Using env_json on a public repository will otherwise expose this data. To securely pass an environment use the env_file option.

merge_env: If this is true and the stack exists, will update the existing Env with the provided env_json/env_file. If you are not providing an env, the existing env will be used, and you do not need to set this.

username/password: Only set these if the repo is private and requires authentication. This is NOT the Portainer username/password, 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. See the docs for more info.

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

- 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

💡 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_json, 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_json: '{"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_json: '{"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_json: |
      {
        "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 Action'

on:
  workflow_dispatch:
    inputs:
      tags:
        description: 'Tags: comma,separated'
        required: true
        default: 'latest'

env:
  REGISTRY: 'ghcr.io'

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@v4

    - name: 'Setup Buildx'
      uses: docker/setup-buildx-action@v2
      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@v4

      - 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]
    permissions:
      contents: read
      packages: write

    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

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.

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.

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

Currently, the best way to contribute to this project is to star this project on GitHub.

If you would like to submit a PR, please review the CONTRIBUTING.md.

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

For a full list of current projects to support visit: https://cssnr.github.io/