Overview
Verifieddit (verifieddit.com) is a privacy-first media verification platform using C2PA Content Credentials. The frontend is a Vite SPA served by nginx. Deployment follows the Sovereign Architecture SOP: Nix build, Skopeo push, Pulumi deploy.
Repository: Sanmarcsoft/verifieddit-www
Architecture
verifieddit-www/
flake.nix -- Nix build (Vite app + Hugo docs + nginx OCI image)
infra/ -- Pulumi IaC for Scaleway deployment
services/
badge-signer/ -- C2PA signing service (Python + c2patool)
stripe-backend/ -- Stripe billing (Bun/TypeScript)
workers/
ddit-shortener/ -- URL shortener Cloudflare Worker
wrangler.toml -- Cloudflare Pages config (badges worker)
Full Deployment Pipeline
Step 1: Build the OCI Image
1
2
3
4
| cd verifieddit-www
# Build the Vite app + nginx OCI image
nix build .#packages.x86_64-linux.oci-image
|
This builds:
- Stage 1: Vite app (bun install, tsc –noEmit, vite build) as a Fixed-Output Derivation
- Stage 2: Hugo docs (if enabled – currently skipped due to PostCSS sandbox issue)
- Final: nginx image with static assets
The Clerk publishable key (VITE_CLERK_PUBLISHABLE_KEY) is baked into the Vite bundle at build time. The key is in the flake.nix.
Step 2: Push to Scaleway Container Registry
1
2
3
4
5
6
7
8
9
10
| # Using the convenience script
nix run .#push-image # pushes as :testing
nix run .#push-image -- prod # pushes as :production
# Or manually
SCW_SECRET=$(pass sanmarcsoft/scaleway/api-secret)
skopeo copy \
docker-archive:./result \
"docker://rg.fr-par.scw.cloud/sanmarcsoft/verifieddit-www:testing" \
--dest-creds "nologin:${SCW_SECRET}"
|
Step 3: Deploy via Pulumi
1
2
3
| cd infra
nix develop .#infra # Enter infra shell with pulumi, skopeo, scw
pulumi up --stack testing
|
Step 4: Verify
1
2
3
4
5
6
| # Check container status
scw container container list | grep verifieddit
# Test the endpoint
curl -s -o /dev/null -w "%{http_code}" https://verifieddit.com/
curl -s https://verifieddit.com/ | head -5
|
Step 5: Purge Cloudflare Cache
After deploying a new version, purge the Cloudflare cache to serve fresh content:
1
2
3
4
5
6
7
8
| CF_TOKEN=$(pass cloudflare/api-token)
ZONE_ID=$(pass cloudflare/zones/verifieddit-com)
curl -s -X POST \
"https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/purge_cache" \
-H "Authorization: Bearer ${CF_TOKEN}" \
-H "Content-Type: application/json" \
--data '{"purge_everything": true}'
|
After testing:
1
2
3
4
5
6
7
8
9
10
11
| # Retag image
SCW_SECRET=$(pass sanmarcsoft/scaleway/api-secret)
skopeo copy \
"docker://rg.fr-par.scw.cloud/sanmarcsoft/verifieddit-www:testing" \
"docker://rg.fr-par.scw.cloud/sanmarcsoft/verifieddit-www:production" \
--src-creds "nologin:${SCW_SECRET}" \
--dest-creds "nologin:${SCW_SECRET}"
# Deploy production
cd infra
pulumi up --stack production
|
Sub-Services Deployment
Badge Signer
1
2
3
4
5
6
7
8
9
10
11
12
| cd services/badge-signer
# Build
nix build .#packages.x86_64-linux.oci-image
# Push
skopeo copy docker-archive:./result \
"docker://rg.fr-par.scw.cloud/sanmarcsoft/verifieddit-badge-signer:testing" \
--dest-creds "nologin:$(pass sanmarcsoft/scaleway/api-secret)"
# Deploy
cd infra && pulumi up
|
Stripe Backend
1
2
3
4
5
6
7
8
9
10
11
12
| cd services/stripe-backend
# Pre-build (required -- dist/ must exist)
bun install && bun run build
# Build OCI image
nix build .#packages.x86_64-linux.oci-image
# Push
skopeo copy docker-archive:./result \
"docker://rg.fr-par.scw.cloud/sanmarcsoft/verifieddit-stripe-backend:testing" \
--dest-creds "nologin:$(pass sanmarcsoft/scaleway/api-secret)"
|
Badges Worker (Cloudflare)
1
2
3
4
5
6
| # Deploy via wrangler
cd verifieddit-www
npx wrangler deploy
# Or for the testing worker
npx wrangler deploy --env testing
|
FOD Hash Updates
When dependencies change (bun.lock, package.json), the FOD hash in flake.nix must be recomputed:
- Edit
flake.nix, set outputHash = pkgs.lib.fakeHash; - Run
nix build .#packages.x86_64-linux.oci-image 2>&1 | grep "got:" - Copy the printed hash and replace
fakeHash with it - Rebuild to verify
Environment Variables
| Variable | Source | Description |
|---|
VITE_CLERK_PUBLISHABLE_KEY | Baked in flake.nix | Clerk frontend key (pk_live_…) |
PORT | Container config | nginx listen port (default: 8080) |
NODE_ENV | Container config | Always “production” |
Known Issues
- Hugo docs build skipped: PostCSS in Nix sandbox is not yet resolved. Docs are currently not included in the OCI image.
- Clerk key change requires hash recompute: Changing the Clerk publishable key changes the Vite build output, invalidating the FOD hash.