LinkDen
Self-Hosting

Cloudflare Deployment

Step-by-step guide to deploying LinkDen on Cloudflare Workers and D1.

Cloudflare Deployment

LinkDen is designed to run entirely on the Cloudflare stack. This guide walks through the complete deployment process, from account setup to a live, production-ready instance.

Architecture Overview

LinkDen deploys two Cloudflare Workers and a D1 database:

┌──────────────────────────┐     ┌──────────────────────┐     ┌──────────────┐
│  Cloudflare Worker       │     │  Cloudflare Worker    │     │ Cloudflare D1│
│  linkden-web             │────▶│  linkden-api          │────▶│ (SQLite DB)  │
│  (Next.js via OpenNext)  │     │  (Hono API + tRPC)    │     │              │
└──────────────────────────┘     └──────────────────────┘     └──────────────┘
ServicePurposeFree Tier
linkden-web WorkerNext.js frontend (SSR + static assets via OpenNext)100,000 requests/day
linkden-api WorkerHono API server with tRPC100,000 requests/day
Cloudflare D1SQLite database for all application data5M rows read/day, 100K writes/day

The web frontend is deployed using @opennextjs/cloudflare, which compiles the full Next.js app (including dynamic routes and SSR) into a Cloudflare Worker.

The free tier is sufficient for most single-user instances.

Prerequisites

Before you begin, make sure you have the following:

  1. A Cloudflare account (free tier is sufficient).
  2. Node.js 20+ installed on your machine.
  3. pnpm 9+ installed globally (npm install -g pnpm).
  4. The LinkDen repository cloned locally:
git clone https://github.com/mrdemonwolf/linkden.git
cd LinkDen
pnpm install

Step 1: Create a Cloudflare API Token

The API token allows LinkDen's deployment tooling (Alchemy + Wrangler) to manage Cloudflare resources on your behalf.

  1. Log in to the Cloudflare dashboard.
  2. Click your profile icon in the top-right corner.
  3. Go to My Profile > API Tokens.
  4. Click Create Token.
  5. Select the Custom token template.
  6. Configure the following permissions:
Permission ScopeResourceAccess Level
AccountCloudflare Workers ScriptsEdit
AccountD1Edit
AccountWorkers KV StorageEdit
  1. Under Account Resources, select your account.
  2. Click Continue to summary, then Create Token.
  3. Copy the token immediately. It is shown only once. Store it securely.

Step 2: Find Your Account ID

  1. Go to the Cloudflare dashboard overview page.
  2. Your Account ID is displayed in the right sidebar under Account details.
  3. Copy it.

Alternatively, use the Wrangler CLI:

npx wrangler whoami

Step 3: Install and Authenticate Wrangler CLI

Wrangler is Cloudflare's CLI tool. Authenticate with your Cloudflare account:

wrangler login

This opens a browser window for OAuth authentication. To verify:

wrangler whoami

Step 4: Configure Environment Variables

Create a .env file in the project root:

# Cloudflare credentials
CLOUDFLARE_API_TOKEN=your-api-token-here
CLOUDFLARE_ACCOUNT_ID=your-account-id-here

# App URLs (update after first deploy with your actual worker URLs)
NEXT_PUBLIC_SITE_URL=https://linkden-web.<your-subdomain>.workers.dev
NEXT_PUBLIC_API_URL=https://linkden-api.<your-subdomain>.workers.dev
APP_URL=https://linkden-api.<your-subdomain>.workers.dev
CORS_ORIGIN=https://linkden-web.<your-subdomain>.workers.dev

# Authentication (choose one or both)
CF_ACCESS_TEAM_DOMAIN=your-team-domain
# OR
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_your-key
CLERK_SECRET_KEY=sk_live_your-key

See the Environment Variables reference for a full list of all variables.

Step 5: Create the D1 Database and Push Schema

Create the D1 database:

npx wrangler d1 create linkden-db

Copy the database_id from the output and update apps/server/wrangler.toml:

[[d1_databases]]
binding = "DB"
database_name = "linkden-db"
database_id = "your-database-id-here"

Then push the schema. You can use drizzle-kit with a CLOUDFLARE_API_TOKEN:

CLOUDFLARE_ACCOUNT_ID=your-account-id CLOUDFLARE_API_TOKEN=your-token pnpm db:push

Or push directly via wrangler (which uses your wrangler login session):

npx wrangler d1 execute linkden-db --remote --file=packages/db/drizzle/schema.sql

Step 6: Deploy

With everything configured, deploy the entire stack:

pnpm cf:deploy

This command:

  1. Deploys the linkden-api Worker (Hono API + tRPC) with D1 bindings via wrangler.
  2. Builds the Next.js app via @opennextjs/cloudflare (OpenNext).
  3. Deploys the linkden-web Worker with static assets via wrangler.

The deploy output shows your live URLs:

API:  https://linkden-api.<your-subdomain>.workers.dev
Web:  https://linkden-web.<your-subdomain>.workers.dev

You can also deploy each part independently:

# Deploy only the API worker
pnpm cf:deploy:api

# Deploy only the web frontend
pnpm cf:deploy:web

Step 7: Set Up Authentication

LinkDen supports Cloudflare Access and/or Clerk for admin authentication. For Cloudflare deployments, Cloudflare Access is recommended — it protects your admin panel at the edge before requests reach your Worker.

Quick Cloudflare Access Setup

  1. Go to Zero Trust in the Cloudflare dashboard.
  2. Set up a team name (e.g., mycompany) if you have not already.
  3. Go to Settings > Authentication and add an identity provider (One-time PIN is the simplest).
  4. Go to Access > Applications > Add an application > Self-hosted.
  5. Set the application domain to linkden-web.<your-subdomain>.workers.dev with path /admin.
  6. Create an access policy that allows only your email address.
  7. Set CF_ACCESS_TEAM_DOMAIN in apps/server/wrangler.toml:
[vars]
CF_ACCESS_TEAM_DOMAIN = "mycompany"

Then redeploy: pnpm cf:deploy:api

See the Authentication guide for detailed instructions, Clerk setup, or using both providers together.

Step 8: Set Secrets (Optional)

If using Clerk or Resend, set their secret keys as encrypted secrets on the API Worker:

cd apps/server
echo "sk_live_your-key" | wrangler secret put CLERK_SECRET_KEY
echo "re_your-key" | wrangler secret put RESEND_API_KEY

Or via the Cloudflare dashboard: Workers & Pages > linkden-api > Settings > Variables.

Step 9: Verify the Deployment

  1. Visit your web Worker URL. You should see the public link page.
  2. Navigate to /admin and confirm the sign-in flow works.
  3. Sign in and try creating a link to verify the API connection.
  4. Check the analytics dashboard.

If something is not working, check the Worker logs:

wrangler tail linkden-api
wrangler tail linkden-web

Step 10: Set Up Custom Domains (Optional)

See the Custom Domain guide for detailed instructions.

To add custom domains to your Workers:

  1. Go to Workers & Pages in the Cloudflare dashboard.
  2. Click on your Worker (linkden-web or linkden-api).
  3. Go to Settings > Domains & Routes.
  4. Add your custom domain.

After adding custom domains, update your environment variables:

NEXT_PUBLIC_SITE_URL=https://links.yourdomain.com
NEXT_PUBLIC_API_URL=https://api.yourdomain.com
APP_URL=https://api.yourdomain.com
CORS_ORIGIN=https://links.yourdomain.com

Then redeploy: pnpm cf:deploy

Subsequent Deploys

After the initial setup, deploying updates is a single command:

git pull origin main
pnpm cf:deploy

Alchemy only updates resources that changed, making subsequent deploys faster.

SSL/TLS Configuration

Cloudflare provides SSL/TLS automatically for all Workers deployments:

  • Default *.workers.dev subdomains: Full SSL is enabled by default.
  • Custom domains on Cloudflare DNS: Cloudflare automatically provisions and renews SSL certificates.

No manual certificate management is required.

Monitoring and Logs

Real-Time Logs

Stream live logs from your Workers:

# API logs
wrangler tail linkden-api

# Web frontend logs
wrangler tail linkden-web

# Only errors
wrangler tail linkden-api --status error

Cloudflare Dashboard Analytics

  1. Workers analytics: Workers & Pages > your Worker > Analytics.
  2. D1 analytics: Workers & Pages > D1 > your database.

Tearing Down

To remove all Cloudflare resources created by Alchemy:

pnpm cf:destroy

This deletes the API Worker and the D1 database. The web Worker must be deleted separately from the Cloudflare dashboard.

This action is irreversible. Back up your D1 database first:

wrangler d1 export linkden-db --output backup.sql

Troubleshooting

"Authentication error" on deploy

Your API token may have expired or lack the required permissions.

wrangler whoami

CORS errors in the browser console

The CORS_ORIGIN environment variable on the API Worker must exactly match the origin of your web app (including protocol, no trailing slash).

# Correct
CORS_ORIGIN=https://linkden-web.your-subdomain.workers.dev

# Wrong
CORS_ORIGIN=https://linkden-web.your-subdomain.workers.dev/

Worker returns 1101 error

Check the Worker logs:

wrangler tail linkden-api --status error

Common causes: missing environment variable, D1 binding not configured, auth key mismatch.

Cost Considerations

For a typical single-user link page:

MetricTypical UsageFree Tier Limit
Worker requests (both)~10K/month100,000/day each
D1 rows read~300/day5,000,000/day
D1 rows written~50/day100,000/day
D1 storage< 50 MB5 GB

Most LinkDen instances will operate entirely within the free tier indefinitely.

On this page