Deploy to IPFS Action

April 7, 2026 ยท View on GitHub

This GitHub Action automates the deployment of static sites to IPFS using CAR files. It pins to either IPFS Cluster or a single Kubo instance, as well as supporting additional pinning to Pinata and Filebase. The action will automatically create a preview link and update your PR/commit status with the deployment information.

This action is built and maintained by Interplanetary Shipyard.

The composite action makes no assumptions about your build process. You should just run your build and then call this action (as a step in an existing job) with the path-to-deploy input set to the path of your build output directory.

Setting commit status

PR comment with CID and preview links

Table of Contents

Features

  • ๐Ÿ“ฆ Merkleizes your static site into a CAR file
  • ๐Ÿš€ Uploads CAR file to either IPFS Cluster or a single Kubo instance
  • ๐Ÿ“ Optional pinning to Pinata
  • ๐Ÿ’พ Optional CAR file upload to Filebase
  • ๐Ÿ“ค CAR file attached to Github Action run Summary page
  • ๐Ÿ”— Automatic preview links
  • ๐Ÿ’ฌ Optional PR comments with CID and preview links
  • โœ… Optional commit status updates with build CID

How does this compare to the other IPFS actions?

This action encapsulates the established best practices for deploying static sites to IPFS.

  • Merkleizes the build into a CAR file in GitHub Actions using Kubo. This ensures that the CID is generated in the build process and is the same across multiple providers.
  • Uploads the CAR file to IPFS via IPFS Cluster or a single Kubo instance.
  • Optionally pins the CID of the CAR file to Pinata. This is useful for redundancy (multiple providers). The pinning here is done in the background and non-blocking.
  • Updates the PR/commit status with the deployment information and preview links.

Inputs

Required Inputs

InputDescription
path-to-deployPath to the directory containing the frontend build to merkleize into a CAR file and deploy to IPFS
github-tokenGitHub token for updating commit status and PR comments

CAR Upload Provider (at least one required)

Important

You must configure at least one of the CAR upload providers below. Pinata cannot be the sole provider until CAR upload support is tested (ipshipyard/ipfs-deploy-action#42).

IPFS Cluster

ipfscluster.io

InputDescription
cluster-urlIPFS Cluster URL to pass to ipfs-cluster-ctl --host
cluster-userIPFS Cluster username for basic http auth
cluster-passwordIPFS Cluster password for basic http auth

Kubo

github.com/ipfs/kubo ยท RPC API docs

InputDescription
kubo-api-urlKubo RPC API URL to pass to ipfs --api, e.g. /dns/YOUR_DOMAIN/tcp/443/https
kubo-api-authKubo RPC API auth secret to pass to ipfs --api-auth, e.g. basic:hello:world (defined as AuthSecret in API.Authorizations config)

Filebase

filebase.com

InputDescription
filebase-access-keyFilebase access key
filebase-secret-keyFilebase secret key
filebase-bucketFilebase bucket name

Storacha (deprecated)

Warning

Storacha uploads will stop working on April 15, 2026. Please switch to an alternative provider.

InputDescription
storacha-keyStoracha base64 encoded key
storacha-proofStoracha Base64 encoded proof UCAN

Optional Inputs

InputDescriptionDefault
node-versionNode.js version to use'20'
cluster-ctl-versionIPFS Cluster CLI version to use'v1.1.2'
kubo-versionKubo CLI version to use for pinning API and CAR uploads'v0.33.0'
ipfs-add-optionsOptions to pass to ipfs add command that is used to merkleize the build. See ipfs add docs'--cid-version 1 --chunker size-1048576'
pinata-pinning-urlPinata Pinning Service URL'https://api.pinata.cloud/psa'
pinata-jwt-tokenPinata JWT token for authentication-
set-github-statusSet GitHub commit status with build CID. Use "true" or "false" (as strings)'true'
set-pr-commentSet PR comments with IPFS deployment information. Use "true" or "false" (as strings)'true'
github-status-gwGateway to use for the links in commit status updates (The green checkmark with the CID)'inbrowser.link'
upload-car-artifactUpload and publish the CAR file on GitHub Action Summary pages'true'
cluster-retry-attemptsNumber of retry attempts for IPFS Cluster uploads'5'
cluster-timeout-minutesTimeout in minutes for each IPFS Cluster upload attempt'2'
cluster-pin-expire-inTime duration after which the pin will expire in IPFS Cluster (e.g. 720h for 30 days). If unset, the CID will be pinned with no expiry.-
pin-nameCustom name for the pin. If unset, defaults to "{repo-name}-{commit-sha-short}" for both IPFS Cluster and Pinata.-

Outputs

OutputDescription
cidThe IPFS CID of the uploaded content

Usage

Simple Workflow (No Fork PRs)

For repositories that don't accept PRs from forks, you can use a single workflow:

name: Build and Deploy to IPFS

permissions:
  contents: read
  pull-requests: write
  statuses: write

on:
  push:
    branches:
      - main
  pull_request:

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    outputs:
      cid: ${{ steps.deploy.outputs.cid }}
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Build project
        run: npm run build

      - name: Deploy to IPFS
        uses: ipfs/ipfs-deploy-action@v1
        id: deploy
        with:
          path-to-deploy: out
          cluster-url: ${{ secrets.CLUSTER_URL }}
          cluster-user: ${{ secrets.CLUSTER_USER }}
          cluster-password: ${{ secrets.CLUSTER_PASSWORD }}
          github-token: ${{ github.token }}

Dual Workflows (With Fork PRs)

For secure deployments of PRs from forks, use two separate workflows that pass artifacts between them:

.github/workflows/build.yml - Builds without secrets access:

name: Build

permissions:
  contents: read

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

env:
  BUILD_PATH: 'out'  # Update this to your build output directory

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}


      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Build project
        run: npm run build

      - name: Upload build artifact
        uses: actions/upload-artifact@v4
        with:
          name: website-build-${{ github.run_id }}
          path: ${{ env.BUILD_PATH }}
          retention-days: 1

.github/workflows/deploy.yml - Deploys with secrets access:

name: Deploy

permissions:
  contents: read
  pull-requests: write
  statuses: write

on:
  workflow_run:
    workflows: ["Build"]
    types: [completed]

env:
  BUILD_PATH: 'website-build'  # Directory where artifact from build.yml will be unpacked

jobs:
  deploy-ipfs:
    if: github.event.workflow_run.conclusion == 'success'
    runs-on: ubuntu-latest
    outputs:
      cid: ${{ steps.deploy.outputs.cid }}
    steps:
      - name: Download build artifact
        uses: actions/download-artifact@v4
        with:
          name: website-build-${{ github.event.workflow_run.id }}
          path: ${{ env.BUILD_PATH }}
          run-id: ${{ github.event.workflow_run.id }}
          github-token: ${{ github.token }}

      - name: Deploy to IPFS
        uses: ipfs/ipfs-deploy-action@v1
        id: deploy
        with:
          path-to-deploy: ${{ env.BUILD_PATH }}
          cluster-url: ${{ secrets.CLUSTER_URL }}
          cluster-user: ${{ secrets.CLUSTER_USER }}
          cluster-password: ${{ secrets.CLUSTER_PASSWORD }}
          github-token: ${{ github.token }}

Warning

Do not use github.event.workflow_run.head_branch alone for production deployment gates. In workflow_run context, head_branch reflects the branch name of the triggering workflow, which is controlled by the PR author. A fork PR with a branch named main will pass a head_branch == 'main' check. For production-only jobs (e.g., GitHub Pages deploy, DNSLink update), always gate on github.event.workflow_run.event == 'push' in addition to the branch name. Only repository collaborators with write access can trigger push events on the default branch.

  production-deploy:
    needs: deploy-ipfs
    if: |
      github.event.workflow_run.event == 'push' &&
      github.event.workflow_run.head_branch == 'main'

See GitHub docs on security hardening and GitHub Security Lab: keeping workflows secure for more details.

See real-world examples:

  • IPFS Specs - Uses the secure two-workflow pattern
  • IPFS Docs - Uses the secure two-workflow pattern

FAQ

  • How can I safely build on PRs from forks?
    • Use the two-workflow pattern shown above. The build workflow runs on untrusted fork code without secrets access, while the deploy workflow only runs after a successful build and has access to secrets but never executes untrusted code. This pattern uses GitHub's workflow_run event to securely pass artifacts between workflows. If your deploy workflow has production-only jobs gated on a branch name, always check github.event.workflow_run.event == 'push' rather than relying solely on github.event.workflow_run.head_branch, since fork PRs can use any branch name.
  • Why should I check workflow_run.event == 'push' for production deployments?
    • In a workflow_run event, head_branch reflects the branch name of the triggering workflow run. For fork PRs, this is the fork's branch name, which is controlled by the PR author. A fork PR with a branch named main will pass head_branch == 'main'. Checking event == 'push' is safe because only collaborators with write access can push to the default branch. See GitHub Security Lab for details.
  • What's the difference between uploading a CAR and using the Pinning API?
    • Since the CAR is like a tarball of the full build with some additional metadata (merkle proofs), the upload will be as big as the build output. Pinning with the Pinning API in contrast is just a request to instruct the pinning service to retrieve and pin the data. CAR uploads are supported by Kubo, IPFS Cluster, and Filebase.
  • How can I update DNSLink?