wakqasahmed.eth
← All projects
Case Study Featured

Decentralized CI/CD for Web3 Publishing

A production-grade deployment pipeline that builds, tests, and publishes a static site to IPFS — with ENS contenthash updates on merge — eliminating any dependency on centralised hosting.

IPFSENSGitHub ActionsNetlifyPinataWeb3CI/CDEthereumDevOps
Visit Site → Source Code

Executive Summary

Most Web3 projects talk about decentralisation but deploy to Vercel. This project ships the full stack: a static portfolio site that is built by GitHub Actions, tested with Vitest and Playwright, pinned to IPFS via Pinata, and made globally accessible through an ENS .eth domain — with an eth.limo HTTPS fallback for users without a Web3 browser. Netlify handles staging previews on every pull request; production publishes only on a merge to main, at which point the ENS contenthash record is updated on-chain. The result is a content pipeline with no single point of failure, no hosting bill, and a verifiable, immutable audit trail on Ethereum mainnet.


Problem → Solution → Outcome

Problem

Publishing a website typically means trusting a centralised host — Vercel, Netlify, AWS S3 — to keep your content available. That dependency is invisible until it isn’t: accounts get suspended, regions go down, providers change pricing. For a developer building in Web3, deploying to a Web2 CDN is an architectural contradiction.

Solution

Build a CI/CD pipeline where the production publish step is IPFS pinning and an ENS contenthash update, not an FTP upload or a git push to a platform. Every code change triggers automated tests. Every pull request gets a Netlify preview for human review. Every merge to main produces a deterministic build, pins it to IPFS, and updates the ENS record — so the domain always resolves to the latest content without anyone manually touching DNS or a dashboard.

Outcome

A fully automated, decentralised publishing workflow where:


Architecture

Pull Request / Staging Workflow

Every branch push triggers a GitHub Actions build job and unit + e2e test jobs. If tests pass, Netlify automatically generates a Deploy Preview at a unique URL. Pull request reviewers get a live, isolated preview environment with no manual deployment step. Nothing reaches IPFS at this stage — Netlify is used exclusively for ephemeral preview and is considered a convenience layer, not part of the production path.

Branch push
  → GitHub Actions: build + test
  → Netlify: Deploy Preview URL (staging only)
  → PR review on preview URL

Test Execution Before Deploy

Two test layers run in CI before any build artifact is used:

Production deploys are blocked if either layer fails.

Build Step

astro build generates a deterministic dist/ directory of pure static HTML, CSS, and minimal JavaScript. No server-side rendering, no Node.js runtime dependency at serve time. The output is a flat directory tree suitable for direct IPFS upload.

IPFS Upload and Pinning

On merge to main, a GitHub Actions job uploads the dist/ directory to IPFS using the Pinata API. Pinata returns a CID (content identifier) — a SHA-256-based hash of the entire directory tree. The CID is deterministic: the same content always produces the same CID, making every deployment independently verifiable.

Pinata holds a persistent pin, ensuring the content survives garbage collection across the IPFS network. A secondary pin can be added to a self-hosted IPFS node or Web3.Storage for redundancy with no additional workflow changes.

Merge to main
  → astro build → dist/
  → Pinata API: upload dist/ → CID
  → Pin confirmed

ENS contenthash Update

With the CID in hand, the pipeline calls the ENS Public Resolver on Ethereum mainnet to update the contenthash record for wakqasahmed.eth to ipfs://<CID>. This is a single on-chain transaction signed by a deployer wallet whose private key is stored as a GitHub Actions secret.

This step is intentionally gated to main branch merges only — not to every commit, not to staging, not to tags. The reason is straightforward: each ENS update costs gas. Triggering it on every push would be wasteful and would expose the deployer key to more signing events than necessary. Merging to main is the deliberate release action; the gas cost is the economic signal that this is a real publish event.

CID confirmed
  → ENS Public Resolver: setContenthash(wakqasahmed.eth, ipfs://CID)
  → Transaction broadcast → confirmed on mainnet
  → wakqasahmed.eth resolves to new content globally

eth.limo Fallback

For users without a Web3-capable browser or IPFS extension, https://wakqasahmed.eth.limo acts as an HTTPS gateway that resolves the ENS name and serves the IPFS content over HTTP. This is a read-only fallback — it introduces no centralised write dependency and can be swapped for any other ENS gateway (e.g., eth.link, a self-hosted gateway) without changing the pipeline.

Why This Is Better Than Manual Publishing

Manual publishingThis pipeline
Developer remembers to deployMerge to main triggers deploy automatically
Hosting provider can suspend accountContent is on IPFS — no account to suspend
Previous versions may not be retrievableEvery CID is permanently accessible
No audit trailEvery deployment is an on-chain transaction
”Works on my machine” stagingIsolated Netlify preview per PR
DNS propagation delayENS update effective within ~1 block (~12 seconds)

Case Study

Challenge

Personal portfolio sites are typically hosted on Vercel or Netlify and forgotten. For a developer who works in Web3 and talks about decentralisation to clients, deploying to a Web2 CDN while claiming expertise in decentralised infrastructure is a credibility gap. The challenge was to build a pipeline that is genuinely decentralised in production — not as a proof of concept, but as the live, maintained deployment of a real site.

Requirements

Implementation

The pipeline is implemented as three GitHub Actions workflows:

  1. ci.yml — runs on every push to main and staging, and on every pull request targeting those branches. Jobs: build, unit, e2e. The e2e job depends on build completing successfully. Pull requests cannot be merged if this workflow fails.

  2. deploy-ipfs.yml — runs only on push to main after ci.yml passes. Steps: build → upload to Pinata → extract CID → call ENS resolver.

  3. Netlify is connected to the repository via its GitHub App. Deploy Previews are generated automatically for every pull request with no workflow YAML required. Netlify is explicitly not used for production.

Content is authored as Markdown files with typed frontmatter, validated at build time by Astro’s content collections. Schema violations fail the build before any test runs.

Trade-offs

Netlify for staging vs. a self-hosted preview environment Netlify was chosen for staging previews because it provides isolated, shareable URLs per pull request with no infrastructure overhead. The trade-off is a dependency on Netlify for the review workflow. This is an acceptable risk: Netlify going down delays reviews but does not affect production. The production path has no Netlify dependency.

Pinata for pinning vs. self-hosted IPFS node Pinata is a managed pinning service. Using it introduces a dependency on a third party for pin persistence. The mitigation is that IPFS content-addressing means the content itself is not controlled by Pinata — only the pin is. If Pinata were to disappear, re-pinning the content elsewhere using the known CID would restore availability. A secondary pin on a self-hosted node or Web3.Storage would eliminate this risk entirely.

ENS updates on mainnet vs. a cheaper L2 ENS on Ethereum mainnet is the most widely supported option for .eth resolution. L2 ENS resolvers exist but have inconsistent gateway support. For infrequent updates (once per release), mainnet gas costs are manageable and the trade-off of broader compatibility is worth it.

Deterministic builds IPFS CIDs are sensitive to file metadata. Builds are pinned to a specific Node.js version in CI to prevent CID drift between local and CI environments. Non-deterministic builds would result in a different CID each time, requiring an ENS update even for identical content — wasting gas.

Security Considerations

Gas Fee Considerations

ENS contenthash updates cost approximately 45,000–65,000 gas, which at typical mainnet conditions amounts to a few cents to a few dollars depending on network congestion. This pipeline triggers the update exactly once per merge to main. A project with one release per week would spend well under $10/month on gas. Teams releasing multiple times per day should evaluate batching releases or using a cheaper resolver.

Reliability Considerations


What I Built


Why This Matters

For technical leads and engineering teams: This demonstrates the ability to design and operate a deployment pipeline end to end — not just write application code. The pipeline handles test automation, artifact management, third-party API integration (Pinata), on-chain transactions (ENS), and secrets management, all wired together in a reproducible CI/CD workflow. Every component has a documented reason for being there and a documented trade-off for the alternative.

For founders building in Web3: Most Web3 projects have a centralised deployment problem they haven’t addressed. This pipeline is a concrete reference implementation of how to publish without a hosting dependency — with a .eth domain that works in Web3 browsers natively and degrades gracefully to HTTPS for everyone else. It can be adapted to any static frontend in a weekend.

For recruiters: The candidate built a real system with real constraints (gas costs, IPFS determinism, secret scoping, test gating) and shipped it. It is live. You can visit wakqasahmed.eth.limo and see the result. This is not a tutorial project. The pipeline is documented, version-controlled, and maintained.


Technologies Used


Results

MetricValue
Time from merge to IPFS pin confirmed< 3 minutes
Time from IPFS pin to ENS resolution live< 1 block (~12 seconds after tx confirm)
Test coverage (routes)6 / 6 routes covered by e2e
Unit test assertions9 content schema assertions
Gas cost per release (ENS update)45,000–65,000 gas ($0.50–$3.00 at typical conditions)
Single points of failure in production path0
Hosting providers that can take the site down0
Previous versions retrievable by CIDAll

Next Steps