This GitHub Action sets the DNSLink TXT record for a given domain.
Supports the following DNS providers:
- Cloudflare
- DNSimple
- Gandi
This action is built and maintained by Interplanetary Shipyard.

It's a composite action that can be called as a step in your workflow to set the DNSLink TXT record for a given domain.
| Input | Description |
|---|---|
cid |
CID of the build to update the DNSLink for |
dnslink_domain |
Domain to update the DNSLink for e.g. if you set docs.ipfs.tech, the _dnslink.docs.ipfs.tech TXT record will be updated |
cf_zone_id |
Cloudflare Zone ID |
cf_auth_token |
Cloudflare API token |
dnsimple_token |
DNSimple API token |
dnsimple_account_id |
DNSimple account ID |
gandi_pat |
Gandi Personal Authorization Token (Bearer auth) |
gandi_rrset_name |
Name of the record for which to update the DNSLink record, e.g. mysubdomain, _dnslink. will be prepended) |
| Input | Description |
|---|---|
cf_record_id |
Cloudflare Record ID. If omitted, the action auto-discovers or creates the record (see below) |
set_github_status |
Set the GitHub commit status with the DNSLink domain and CID |
github_token |
GitHub token |
- Log in to the Cloudflare dashboard
- Select your domain
- On the Overview page, scroll down to the API section in the right sidebar
- Copy the Zone ID and save it as
CF_DNS_ZONE_IDsecret in your repository
See Cloudflare docs for more details.
- Go to API Tokens in your Cloudflare profile
- Click Create Token
- Select Create Custom Token
- Configure the token:
- Token name: e.g.,
dnslink-action for example.com - Permissions:
Zone/DNS/Edit - Zone Resources:
Include/Specific zone/ select your domain
- Token name: e.g.,
- Click Continue to summary, then Create Token
- Copy the token value and save it as
CF_DNS_AUTH_TOKENsecret in your repository
See Cloudflare video tutorial for a walkthrough.
For enhanced security, you can isolate DNSLink records on a separate domain. This ensures that even if your API token is compromised, attackers can only modify TXT records on the sandboxed domain, not your main domain.
Setup:
- Create a dedicated zone for DNSLink records (e.g.,
dnslinks.example.com) - Create an API token scoped only to that zone
- On your main domain, add a CNAME pointing to the sandboxed record:
_dnslink.docs.yourdomain.com CNAME _dnslink.docs.dnslinks.example.com - Set
dnslink_domainto the sandboxed domain (not your main domain):dnslink_domain: docs.dnslinks.example.com
The action updates _dnslink.docs.dnslinks.example.com, and the CNAME redirects lookups from your main domain.
When cf_record_id is not provided, the action automatically manages the DNS record:
- Queries Cloudflare for existing TXT records named
_dnslink.{dnslink_domain} - If no record exists: creates a new TXT record
- If one record exists: updates it with the new CID
- If multiple records exist: fails with an error asking you to manually remove duplicates
This eliminates the need to manually look up and configure the record ID.
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: # This exposes the CID output of the action to the rest of the workflow
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: ipshipyard/ipfs-deploy-action@v1
id: deploy
with:
path-to-deploy: out
storacha-key: ${{ secrets.STORACHA_KEY }}
storacha-proof: ${{ secrets.STORACHA_PROOF }}
github-token: ${{ github.token }}
- name: Update DNSLink
uses: ipshipyard/dnslink-action@v1
if: github.ref == 'refs/heads/main' # only update the DNSLink on the main branch
with:
cid: ${{ steps.deploy.outputs.cid }}
dnslink_domain: mydomain.com
cf_zone_id: ${{ secrets.CF_ZONE_ID }}
cf_auth_token: ${{ secrets.CF_AUTH_TOKEN }}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: # This exposes the CID output of the action to the rest of the workflow
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: ipshipyard/ipfs-deploy-action@v1
id: deploy
with:
path-to-deploy: ${{ env.BUILD_PATH }}
storacha-key: ${{ secrets.STORACHA_KEY }}
storacha-proof: ${{ secrets.STORACHA_PROOF }}
github-token: ${{ github.token }}
- name: Update DNSLink
uses: ipshipyard/dnslink-action@v1
if: github.event.workflow_run.head_branch == 'main' # only update for main branch
with:
cid: ${{ steps.deploy.outputs.cid }}
dnslink_domain: mydomain.com
cf_zone_id: ${{ secrets.CF_ZONE_ID }}
cf_auth_token: ${{ secrets.CF_AUTH_TOKEN }}
github_token: ${{ github.token }}
set_github_status: true- Why not use an infrastructure-as-code tool like OctoDNS or DNSControl?
- You can! Those are great tools.
- 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_runevent to securely pass artifacts between workflows.
- 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