Inspired by the cookbook github action deployment guide, permaweb-deploy is a Node.js command-line tool designed to streamline the deployment of web applications to the permaweb using Arweave. It uploads your build folder or a single file, creates Arweave manifests, and updates ArNS (Arweave Name Service) records via ANT (Arweave Name Token) with the transaction ID.
- Turbo SDK Integration: Uses Turbo SDK for fast, reliable file uploads to Arweave
- On-Demand Payment: Pay with ARIO or Base-ETH tokens on-demand during upload
- Arweave Manifest v0.2.0: Creates manifests with fallback support for SPAs
- ArNS Updates: Updates ArNS records via ANT with new transaction IDs and metadata
- Automated Workflow: Integrates with GitHub Actions for continuous deployment
- Git Hash Tagging: Automatically tags deployments with Git commit hashes
- 404 Fallback Detection: Automatically detects and sets 404.html as fallback
- Network Support: Supports mainnet, testnet, and custom ARIO process IDs
- Flexible Deployment: Supports deploying a folder or a single file
- Modern CLI: Built with oclif for a robust command-line experience
- TypeScript: Fully typed for better developer experience
Install the package using pnpm (recommended):
pnpm add -D permaweb-deployOr with npm:
npm install --save-dev permaweb-deployOr with yarn:
yarn add --dev permaweb-deploy-
For Arweave signer (default): Encode your Arweave wallet key in base64 format and set it as a GitHub secret:
base64 -i wallet.json | pbcopy -
For Ethereum/Polygon/KYVE signers: Use your raw private key (no encoding needed) as the
DEPLOY_KEY. -
Ensure that the secret name for the encoded wallet or private key is
DEPLOY_KEY.
Command Menu:
Simply run the CLI for an interactive command selector:
permaweb-deploy
# or explicitly
permaweb-deploy interactiveThis shows a menu with options:
- Deploy to Permaweb - Start the deployment wizard
- Show Help - Display help information
- Exit - Exit the CLI
Interactive Deploy (Guided):
Run the deploy command without arguments to be guided through all deployment options:
permaweb-deploy deployThis will prompt you for:
- ArNS name
- Wallet method (file, string, or environment variable)
- Signer type (Arweave, Ethereum, Polygon, KYVE)
- What to deploy (folder or file)
- Advanced options (optional: undername, TTL, network)
Use flags for faster, scriptable deployments:
# Basic deployment with wallet file
permaweb-deploy deploy --arns-name my-app --wallet ./wallet.jsonDeploy using private key directly:
permaweb-deploy deploy --arns-name my-app --private-key "$(cat wallet.json)"Deploy using environment variable:
DEPLOY_KEY=$(base64 -i wallet.json) permaweb-deploy deploy --arns-name my-appDeploy a specific folder:
permaweb-deploy deploy --arns-name my-app --wallet ./wallet.json --deploy-folder ./buildDeploy a single file:
permaweb-deploy deploy --arns-name my-app --wallet ./wallet.json --deploy-file ./path/to/file.txtTo upload a folder or file to Arweave without updating an ArNS name, use the upload command (same Turbo upload, dedupe cache, and payment options as deploy, minus ArNS flags):
permaweb-deploy upload --wallet ./wallet.json --deploy-folder ./dist
permaweb-deploy upload --wallet ./wallet.json --deploy-file ./dist/index.html
DEPLOY_KEY=$(base64 -i wallet.json) permaweb-deploy upload --deploy-folder ./distDeploy to an undername (subdomain):
permaweb-deploy deploy --arns-name my-app --wallet ./wallet.json --undername stagingDeploy with a custom TTL:
permaweb-deploy deploy --arns-name my-app --wallet ./wallet.json --ttl-seconds 7200Deploy using Ethereum wallet (file):
permaweb-deploy deploy --arns-name my-app --sig-type ethereum --wallet ./private-key.txtDeploy using Ethereum wallet (direct key):
permaweb-deploy deploy --arns-name my-app --sig-type ethereum --private-key "0x1234..."Use on-demand payment to automatically fund uploads with ARIO or Base-ETH tokens when your Turbo balance is insufficient:
Deploy with ARIO on-demand payment:
permaweb-deploy deploy --arns-name my-app --wallet ./wallet.json --on-demand ario --max-token-amount 1.5Deploy with Base-ETH on-demand payment (using Ethereum signer):
permaweb-deploy deploy --arns-name my-app --sig-type ethereum --private-key "0x..." --on-demand base-eth --max-token-amount 0.1On-Demand Payment Options:
--on-demand: Token to use for on-demand payment (arioorbase-eth)--max-token-amount: Maximum token amount to spend (in native token units, e.g.,1.5for 1.5 ARIO or0.1for 0.1 ETH)
How it works:
- Checks your Turbo balance before upload
- If balance is insufficient, converts tokens to Turbo credits on-demand
- Automatically adds a 10% buffer (
topUpBufferMultiplier: 1.1) for reliability - Proceeds with upload once funded
Token compatibility:
- ARIO: Works with Arweave signer
- Base-ETH: Works with Ethereum signer (Base Network)
Uploads go through a Turbo bundler service (HTTP API that bundles data for Arweave). By default, permaweb-deploy uses ArDrive’s production bundler (https://upload.ardrive.io). --uploader sets the base URL of the bundler service to use (scheme + host; typically no path).
| When to use | Example value |
|---|---|
| Default (omit flag) | ArDrive production bundler — same as Turbo CLI defaults |
| Arweave bundler | https://up.arweave.net |
| Development / staging | https://upload.ardrive.dev |
| Custom or self-hosted | Your own base URL if it implements the Turbo bundler protocol |
Examples:
# Deploy using Arweave’s bundler service
permaweb-deploy deploy --arns-name my-app --wallet ./wallet.json --uploader https://up.arweave.net
permaweb-deploy upload --wallet ./wallet.json --deploy-folder ./dist --uploader https://up.arweave.netNotes:
- Billing and signer behavior still follow Turbo; if an alternate bundler has different rules, check that provider’s docs.
- Use a base URL only (e.g.
https://up.arweave.net), not a path to a specific file or route.
deploy (ArNS update):
--arns-name, -n(required): The ArNS name to update--ario-process, -p: ARIO process to use (mainnet,testnet, or a custom process ID). Default:mainnet--deploy-folder, -d: Folder to deploy. Default:./dist--deploy-file, -f: Deploy a single file instead of a folder--undername, -u: ANT undername to update. Default:@--ttl-seconds, -t: TTL in seconds for the ANT record (60-86400). Default:60--sig-type, -s: Signer type for deployment. Choices:arweave,ethereum,polygon,kyve. Default:arweave--wallet, -w: Path to wallet file (JWK for Arweave, private key for Ethereum/Polygon/KYVE)--private-key, -k: Private key or JWK JSON string (alternative to--wallet)--on-demand: Enable on-demand payment with specified token. Choices:ario,base-eth--max-token-amount: Maximum token amount for on-demand payment (used with--on-demand)--no-dedupe: Disable deduplication (do not cache or reuse previous uploads)--dedupe-cache-max-entries: Maximum number of entries to keep in the dedupe cache (LRU). Default:10000--uploader: Base URL of the Turbo bundler service to use (default:https://upload.ardrive.io). See Bundler service above.
upload (no ArNS): accepts --deploy-folder, --deploy-file, wallet/signer flags, --uploader, --on-demand / --max-token-amount, and dedupe flags only.
By default, permaweb-deploy caches your deployment log to prevent uploading duplicate (unchanged) files. This saves both time and upload costs by reusing existing data on Arweave.
How it works:
- When you deploy, permaweb-deploy hashes each file in your build
- It checks the local cache for matching hashes from previous uploads
- Files that haven't changed are skipped - the existing transaction ID is reused
- Only new or modified files are uploaded to Arweave
- The cache is stored locally in
.permaweb-deploy/transaction-cache.json
Disable deduplication:
If you need to force a fresh upload of all files (e.g., for debugging or to ensure a completely new deployment):
permaweb-deploy deploy --arns-name my-app --wallet ./wallet.json --no-dedupeLimit cache size:
The dedupe cache uses an LRU (Least Recently Used) eviction strategy. By default, it keeps up to 10,000 entries. You can adjust this limit:
# Keep only the last 1000 file entries
permaweb-deploy deploy --arns-name my-app --wallet ./wallet.json --dedupe-cache-max-entries 1000Cache location:
The cache file is stored at .permaweb-deploy/transaction-cache.json in your project root. You can:
- Add it to
.gitignoreif you don't want to share cache across team members - Commit it to share cached transaction IDs with your team (reduces duplicate uploads)
- Delete it to start fresh:
rm -rf .permaweb-deploy/
Add deployment scripts to your package.json:
{
"scripts": {
"build": "vite build",
"deploy": "pnpm build && permaweb-deploy deploy --arns-name <ARNS_NAME>",
"deploy:staging": "pnpm build && permaweb-deploy deploy --arns-name <ARNS_NAME> --undername staging",
"deploy:testnet": "pnpm build && permaweb-deploy deploy --arns-name <ARNS_NAME> --ario-process testnet",
"deploy:on-demand": "pnpm build && permaweb-deploy deploy --arns-name <ARNS_NAME> --on-demand ario --max-token-amount 1.5"
}
}Then deploy with:
DEPLOY_KEY=$(base64 -i wallet.json) pnpm deployOr with on-demand payment:
DEPLOY_KEY=$(base64 -i wallet.json) pnpm deploy:on-demandThe easiest way to integrate permaweb-deploy into your CI/CD pipeline is using our official GitHub Action.
- uses: permaweb/permaweb-deploy@v1
with:
deploy-key: ${{ secrets.DEPLOY_KEY }}
arns-name: myapp
deploy-folder: ./distAutomatically deploy preview builds for each pull request. The preview mode auto-generates an undername from the PR number and posts a comment with the preview URL:
name: Deploy PR Preview
on:
pull_request:
types: [opened, synchronize]
jobs:
deploy-preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Deploy Preview
uses: permaweb/permaweb-deploy@v1
with:
deploy-key: ${{ secrets.DEPLOY_KEY }}
arns-name: myapp
preview: 'true'
github-token: ${{ secrets.GITHUB_TOKEN }}
deploy-folder: ./distWhen preview is enabled, the action will:
- Auto-generate an undername like
pr-123from the PR number - Post a comment on the PR with the preview URL
- Update the comment on subsequent pushes instead of creating new ones
Deploy to your base ArNS name when pushing to main:
name: Deploy to Production
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Deploy to Permaweb
uses: permaweb/permaweb-deploy@v1
with:
deploy-key: ${{ secrets.DEPLOY_KEY }}
arns-name: myapp
deploy-folder: ./dist- name: Deploy with ARIO on-demand
uses: permaweb/permaweb-deploy@v1
with:
deploy-key: ${{ secrets.DEPLOY_KEY }}
arns-name: myapp
deploy-folder: ./dist
on-demand: ario
max-token-amount: '2.0'By default, the action caches transaction IDs to avoid re-uploading unchanged files. To disable this:
- name: Deploy without dedupe
uses: permaweb/permaweb-deploy@v1
with:
deploy-key: ${{ secrets.DEPLOY_KEY }}
arns-name: myapp
deploy-folder: ./dist
no-dedupe: 'true'You can also limit the cache size:
- name: Deploy with limited cache
uses: permaweb/permaweb-deploy@v1
with:
deploy-key: ${{ secrets.DEPLOY_KEY }}
arns-name: myapp
deploy-folder: ./dist
dedupe-cache-max-entries: '1000'You can also use the CLI directly in your workflows:
Basic Workflow:
name: Deploy to Permaweb
on:
push:
branches:
- main
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install
- run: pnpm deploy
env:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}With On-Demand Payment:
name: Deploy to Permaweb with On-Demand Payment
on:
push:
branches:
- main
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install
- run: pnpm build
- name: Deploy with ARIO on-demand
run: permaweb-deploy deploy --arns-name my-app --on-demand ario --max-token-amount 2.0
env:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
# Or deploy with Ethereum and Base-ETH:
# - name: Deploy with Base-ETH on-demand
# run: |
# permaweb-deploy deploy \
# --arns-name my-app \
# --sig-type ethereum \
# --on-demand base-eth \
# --max-token-amount 0.2
# env:
# DEPLOY_KEY: ${{ secrets.ETH_PRIVATE_KEY }}# Install dependencies
pnpm install
# Build the project
pnpm build
# Run in development mode
pnpm dev
# Run tests
pnpm test
# Run linter
pnpm lint
# Format code
pnpm formatpermaweb-deploy/
├── src/
│ ├── commands/ # oclif commands
│ │ ├── deploy.ts
│ │ └── upload.ts
│ ├── types/ # TypeScript type definitions
│ │ └── index.ts
│ ├── utils/ # Utility functions
│ │ ├── constants.ts
│ │ ├── signer.ts
│ │ ├── uploader.ts
│ │ └── __tests__/ # Unit tests
│ └── index.ts # Main entry point
├── bin/ # Executable scripts
│ ├── run.js
│ └── dev.js
├── .changeset/ # Changesets configuration
├── .husky/ # Git hooks
└── dist/ # Build output
- Dedicated Wallet: Always use a dedicated wallet for deployments to minimize security risks
- Wallet Encoding: Arweave wallets must be base64 encoded to be used in the deployment script
- ArNS Name: The ArNS Name must be passed so that the ANT Process can be resolved to update the target undername or root record
- Turbo Credits: Ensure your wallet has sufficient Turbo Credits, or use on-demand payment for automatic funding
- On-Demand Limits: Set reasonable
--max-token-amountlimits to prevent unexpected costs - Secret Management: Keep your
DEPLOY_KEYsecret secure and never commit it to your repository - Build Security: Always check your build for exposed environmental secrets before deployment, as data on Arweave is permanent
- Error: "DEPLOY_KEY environment variable not set": Verify your base64 encoded wallet is set as the
DEPLOY_KEYenvironment variable - Error: "deploy-folder does not exist": Check that your build folder exists and the path is correct
- Error: "deploy-file does not exist": Check that your build file exists and the path is correct
- Error: "ArNS name does not exist": Verify the ArNS name is correct and exists in the specified network
- Upload timeouts: Files have a timeout for upload. Large files may fail and require optimization
- Insufficient Turbo Credits: Use
--on-demandwith--max-token-amountto automatically fund uploads when balance is low - On-demand payment fails: Ensure your wallet has sufficient tokens (ARIO or Base-ETH) and the token type matches your signer (
ariowith Arweave,base-ethwith Ethereum)
Contributions are welcome! Please follow these guidelines:
- Fork the repository
- Create a feature branch
- Make your changes
- Run tests and linter:
pnpm test && pnpm lint - Create a changeset:
pnpm changeset - Commit your changes using conventional commits
- Push and create a pull request
This project uses Conventional Commits. Commit messages should follow this format:
type(scope): subject
body (optional)
Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
We use changesets for version management. When making changes:
pnpm changesetFollow the prompts to describe your changes.
- @ar.io/sdk - For ANT operations and ArNS management
- @ardrive/turbo-sdk - For fast file uploads to Arweave
- @permaweb/aoconnect - For AO network connectivity
- @oclif/core - CLI framework
- mime-types - MIME type detection
ISC
NickJ202