LinkDen
Self-Hosting

Environment Variables

Complete reference of all environment variables used by LinkDen across the monorepo.

Environment Variables Reference

This page is the definitive reference for every environment variable used by LinkDen. Variables are organized by which .env file they belong to and which service uses them.

Quick Start

LinkDen uses three .env files across the monorepo:

LinkDen/
├── .env                    # Cloudflare deployment credentials
├── apps/
│   ├── web/.env            # Next.js frontend variables
│   └── server/.env         # Hono API server variables

Copy the example files to get started:

# Root (Cloudflare credentials)
cp .env.example .env

# Web app
cp apps/web/.env.example apps/web/.env

# Server
cp apps/server/.env.example apps/server/.env

Root .env

These variables are used by the Alchemy deployment infrastructure in packages/infra to manage Cloudflare resources.

VariableDescriptionRequiredDefault
CLOUDFLARE_API_TOKENCloudflare API token with Workers, Pages, D1, and KV permissionsYes (for Cloudflare deploy)--
CLOUDFLARE_ACCOUNT_IDYour Cloudflare account ID (found on the dashboard overview sidebar)Yes (for Cloudflare deploy)--

These variables are only needed when deploying to Cloudflare. If you are self-hosting with Docker or another platform, you do not need them.

Generating a Cloudflare API Token

  1. Go to Cloudflare API Tokens.
  2. Click Create Token.
  3. Use the Custom token template.
  4. Add these permissions:
ScopeResourceAccess
AccountCloudflare PagesEdit
AccountCloudflare Workers ScriptsEdit
AccountD1Edit
AccountWorkers KV StorageEdit
  1. Select your account under Account Resources.
  2. Click Continue to summary, then Create Token.

Finding Your Account ID

Go to the Cloudflare dashboard. Your Account ID is in the right sidebar under Account details. Alternatively:

npx wrangler whoami

Web App (apps/web/.env)

These variables are used by the Next.js frontend application. Variables prefixed with NEXT_PUBLIC_ are embedded into the client-side JavaScript bundle at build time and are visible to visitors in the browser.

Required Variables

VariableDescriptionExample
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEYClerk publishable key (starts with pk_)pk_live_abc123...
CLERK_SECRET_KEYClerk secret key (starts with sk_). Used during build for middleware.sk_live_abc123...
NEXT_PUBLIC_API_URLFull URL of the Hono API server (no trailing slash)https://api.yourdomain.com
NEXT_PUBLIC_SITE_URLPublic URL of the web app (no trailing slash)https://yourdomain.com

Optional Variables

VariableDescriptionDefault
NEXT_PUBLIC_SITE_NAMEDisplay name for the site (browser title, meta tags, Open Graph)LinkDen
NEXT_PUBLIC_TURNSTILE_SITE_KEYCloudflare Turnstile site key for contact form CAPTCHA widget--
NEXT_PUBLIC_CLERK_SIGN_IN_URLPath to the Clerk sign-in page/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URLPath to the Clerk sign-up page/sign-up

Important Notes on NEXT_PUBLIC_ Variables

  • Variables prefixed with NEXT_PUBLIC_ are baked into the static build at compile time.
  • Changing a NEXT_PUBLIC_ variable requires a full rebuild and redeploy -- the change does not take effect by simply updating the environment variable.
  • Never put secrets in NEXT_PUBLIC_ variables. They are visible to anyone who inspects the JavaScript bundle in the browser.
  • The CLERK_SECRET_KEY in the web app is used during the build process for Next.js middleware. It is not exposed to the client.

Server App (apps/server/.env)

These variables are used by the Hono API server. When deploying to Cloudflare Workers, these are set as Worker environment variables or secrets in the Cloudflare dashboard.

Core (Required)

VariableDescriptionExample
CLERK_SECRET_KEYClerk secret key for JWT verification of authenticated API requestssk_live_abc123...
CLERK_PUBLISHABLE_KEYClerk publishable key for Clerk middlewarepk_live_abc123...
CORS_ORIGINAllowed origin(s) for CORS. Must exactly match the web app URL including protocol. Multiple origins can be comma-separated.https://yourdomain.com
APP_URLPublic URL of the web app. Used for generating absolute URLs (e.g., in emails).https://yourdomain.com

Database

VariableDescriptionRequired
DATABASE_IDCloudflare D1 database ID (only for Cloudflare deployments)Cloudflare only
DATABASE_URLSQLite file path or libSQL URL (for non-Cloudflare self-hosting with Docker, Coolify, Railway, etc.)Non-Cloudflare only

When running on Cloudflare Workers, the D1 database is bound via wrangler.toml as the DB binding, and DATABASE_ID is the D1 database ID.

When self-hosting outside Cloudflare, use DATABASE_URL to point to a local SQLite file:

# Local SQLite file
DATABASE_URL=file:./data/linkden.db

# Remote libSQL (e.g., Turso)
DATABASE_URL=libsql://your-db.turso.io

Email (Optional)

VariableDescriptionExample
RESEND_API_KEYResend API key for sending contact form notification emailsre_abc123...
RESEND_FROM_EMAILVerified sender email address for Resendcontact@yourdomain.com

To use the contact form email notifications:

  1. Sign up at resend.com.
  2. Verify your sending domain.
  3. Generate an API key from the Resend dashboard.
  4. Set both RESEND_API_KEY and RESEND_FROM_EMAIL.

If these are not set, the contact form still works -- submissions are stored in the database but no email notifications are sent.

CAPTCHA (Optional)

VariableDescriptionExample
TURNSTILE_SECRET_KEYCloudflare Turnstile secret key for server-side CAPTCHA verification0x4AAAAAABc...

Used in conjunction with NEXT_PUBLIC_TURNSTILE_SITE_KEY on the web app to protect the contact form from spam.

To set up Turnstile:

  1. Go to Turnstile in the Cloudflare dashboard.
  2. Click Add site.
  3. Enter your domain name.
  4. Choose the widget type (Managed is recommended).
  5. Copy the Site Key (for NEXT_PUBLIC_TURNSTILE_SITE_KEY in the web app).
  6. Copy the Secret Key (for TURNSTILE_SECRET_KEY in the server).

Apple Wallet (Optional)

These variables enable Apple Wallet pass generation. All six must be set for the feature to work.

VariableDescriptionExample
APPLE_PASS_TYPE_IDPass type identifier registered in Apple Developer portalpass.com.yourdomain.linkden
APPLE_TEAM_IDYour Apple Developer team identifier (10-character alphanumeric string)ABC1234DEF
APPLE_WWDR_CERTBase64-encoded Apple Worldwide Developer Relations (WWDR) certificateMIIE...
APPLE_SIGNER_CERTBase64-encoded pass signing certificate (.pem format)MIID...
APPLE_SIGNER_KEYBase64-encoded private key for the signing certificate (.pem format)MIIE...
APPLE_SIGNER_PASSPHRASEPassphrase used when exporting the .p12 signing certificateyour-passphrase

See the Apple Wallet guide for detailed certificate generation instructions.

To base64-encode a certificate or key file:

base64 -i your-certificate.pem | tr -d '\n'

Complete .env Examples

Root .env

# Cloudflare Deployment (only needed for Cloudflare hosting)
CLOUDFLARE_API_TOKEN=your-cloudflare-api-token
CLOUDFLARE_ACCOUNT_ID=your-cloudflare-account-id

apps/web/.env

# Clerk Authentication (required)
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_your-publishable-key
CLERK_SECRET_KEY=sk_live_your-secret-key

# URLs (required)
NEXT_PUBLIC_API_URL=https://api.yourdomain.com
NEXT_PUBLIC_SITE_URL=https://yourdomain.com

# Site Config (optional)
NEXT_PUBLIC_SITE_NAME=My Links
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up

# Turnstile CAPTCHA (optional)
NEXT_PUBLIC_TURNSTILE_SITE_KEY=0x4AAAAAAB...

apps/server/.env

# Clerk Authentication (required)
CLERK_SECRET_KEY=sk_live_your-secret-key
CLERK_PUBLISHABLE_KEY=pk_live_your-publishable-key

# CORS and URLs (required)
CORS_ORIGIN=https://yourdomain.com
APP_URL=https://yourdomain.com

# Database (pick one based on your hosting)
# For Cloudflare D1 -- set via wrangler.toml binding, not env var
# For self-hosted Docker/Coolify/Railway:
# DATABASE_URL=file:./data/linkden.db

# Email Notifications (optional)
RESEND_API_KEY=re_your-api-key
RESEND_FROM_EMAIL=contact@yourdomain.com

# Turnstile CAPTCHA (optional)
TURNSTILE_SECRET_KEY=0x4AAAAAAB...

# Apple Wallet (optional -- all 6 required for wallet feature)
APPLE_PASS_TYPE_ID=pass.com.yourdomain.linkden
APPLE_TEAM_ID=ABC1234DEF
APPLE_WWDR_CERT=base64-encoded-wwdr-cert
APPLE_SIGNER_CERT=base64-encoded-signer-cert
APPLE_SIGNER_KEY=base64-encoded-signer-key
APPLE_SIGNER_PASSPHRASE=your-passphrase

Environment Variables by Hosting Platform

Different hosting platforms have different ways of setting environment variables:

PlatformWeb App VariablesServer Variables
CloudflarePages > Settings > Environment variablesWorkers > Settings > Variables (use "Encrypt" for secrets)
Docker.env file or docker-compose.yml environment block.env file or docker-compose.yml environment block
CoolifyCoolify UI > Service > Environment VariablesCoolify UI > Service > Environment Variables
RailwayRailway dashboard > Service > VariablesRailway dashboard > Service > Variables
VercelVercel dashboard > Project > Settings > Environment VariablesN/A (API not on Vercel)

Security Best Practices

  • Never commit .env files to version control. They are included in .gitignore.
  • Use different Clerk applications for development and production to avoid credential conflicts.
  • Rotate API keys periodically, especially CLERK_SECRET_KEY and RESEND_API_KEY.
  • Use the minimum permissions necessary for Cloudflare API tokens.
  • Encrypt sensitive variables when your platform supports it (Cloudflare Workers "Encrypt" toggle, Railway's built-in encryption).
  • Store production credentials in a secrets manager (1Password, Vault, AWS Secrets Manager) rather than local files.
  • Never put secrets in NEXT_PUBLIC_* variables. These are embedded in the client-side JavaScript bundle and visible to anyone.
  • Audit access regularly. Review who has access to your Cloudflare account, Clerk dashboard, and deployment platforms.

Troubleshooting

"Invalid API key" errors from Clerk

  • Verify the CLERK_SECRET_KEY matches between the web app and server.
  • Ensure you are using the correct environment (development vs. production) keys.
  • Check that the Clerk application has the correct domains configured.

API returns CORS errors

  • CORS_ORIGIN must exactly match the web app URL including the protocol (https://) and must not have a trailing slash.
  • For local development, set CORS_ORIGIN=http://localhost:3001.
  • Multiple origins: CORS_ORIGIN=https://yourdomain.com,https://www.yourdomain.com

"Database not found" errors

  • Cloudflare: Verify the D1 database binding in wrangler.toml matches an existing database. Run wrangler d1 list to check.
  • Self-hosted: Verify DATABASE_URL points to a valid file path and the directory exists.

Environment variable changes not taking effect

  • NEXT_PUBLIC_* variables: These are baked in at build time. You must rebuild and redeploy.
  • Server variables on Cloudflare: After changing Worker secrets, the Worker picks them up on the next request (no redeploy needed for secrets set via dashboard or wrangler secret put).
  • Docker: Restart the container after changing environment variables.

On this page