Deployment and Access Control

This guide covers deploying your Quartz-powered site to Cloudflare Pages and protecting it with Cloudflare Access authentication to share with approved users only.

Overview

What you’ll accomplish:

  • Deploy your site to Cloudflare Pages (free hosting)
  • Set up automatic deployments from GitHub
  • Protect the site with Cloudflare Access authentication
  • Manage approved users with email-based access
  • Configure custom domain (optional)

Time required: 1-2 hours for initial setup

Cost: $0 (using free tiers)


Progress Tracker

Track your progress through the deployment setup:

Deployment Status

  • Part 1: Cloudflare Pages Deployment (Completed 2025-10-26)

    • Step 1: Create Cloudflare Account
    • Step 2: Connect GitHub Repository
    • Step 3: Configure Build Settings
    • Step 4: Monitor First Build
    • Step 5: Verify Deployment & Privacy Settings
    • ✅ Site live at: https://e3059a95.garage-build.pages.dev/
  • Part 2: Cloudflare Access Authentication (Completed 2025-10-26)

    • Step 1: Enable Cloudflare Zero Trust
    • Step 2: Configure Identity Providers (One-time PIN)
    • Step 3: Create Access Application and Policy
    • Step 4: Verify Policy is Attached
    • Step 5: Test Authentication
    • ✅ Authentication working with dangahagan@gmail.com
  • Part 3: Managing Users

    • Add additional users to access policy
    • Test with multiple users
    • Review access logs
  • Part 4: Custom Domain Setup (Completed 2025-10-26)

    • Wait for gahagan.cloud DNS to finish migrating to Cloudflare
    • Configure DNS for garage.gahagan.cloud subdomain
    • Update Access application domain
    • Update Quartz config baseUrl
    • Verify custom domain works
    • ✅ Site live at: https://garage.gahagan.cloud/
  • Part 5: Automatic Deployments (Tested 2025-10-26)

    • Verify push triggers build
    • Monitor build logs
    • Confirm successful deployment
    • ✅ CI/CD pipeline working
  • Part 6: Ongoing Maintenance

    • Regular content updates
    • Monitor build status
    • Review access logs

Prerequisites

Before starting, ensure you have:

  • GitHub repository with your Quartz site (you have this)
  • Cloudflare account (free) - Sign up at https://dash.cloudflare.com/sign-up
  • Local build tested - Verify npx quartz build --serve works
  • Content ready - All content you want to publish is committed to git
  • Privacy review completed - See PII Audit Report

Important: Dual Build Configuration

This site is configured with two different build modes:

  • Local builds include ALL content (contracts, financial info, vendor contacts)
  • Production builds (Cloudflare) automatically exclude sensitive content

This is handled automatically by checking the NODE_ENV environment variable in quartz.config.ts.

See: BUILD-MODES.md in the repository root for details on testing both modes.


Part 1: Cloudflare Pages Deployment

Step 1: Create Cloudflare Account

  1. Go to https://dash.cloudflare.com/sign-up
  2. Create account with email and password
  3. Verify your email address
  4. Log in to Cloudflare Dashboard

Step 2: Connect GitHub Repository

  1. In Cloudflare Dashboard, navigate to Workers & Pages in left sidebar
  2. Click Create application button
  3. Select Pages tab
  4. Click Connect to Git
  5. Click Connect GitHub
  6. Authorize Cloudflare Pages to access your GitHub account
    • You can grant access to all repositories or select specific ones
    • For private repos, choose “Only select repositories” and select garage-build
  7. Click Install & Authorize

Step 3: Configure Build Settings

After connecting GitHub:

  1. Select repository: Choose dgahagan/garage-build (or your repository name)
  2. Select branch: Choose feature/web-presentation (or main if merged)
  3. Click Begin setup

Configure the build:

SettingValue
Project namegarage (or your preferred name - this becomes part of the URL)
Production branchmain or feature/web-presentation
Framework presetNone (leave as “None”)
Build commandnpx quartz build
Build output directorypublic
Root directory/ (leave blank)

Environment variables: None needed for basic setup

  1. Click Save and Deploy

Step 4: Monitor First Build

  1. Watch the build logs in real-time
  2. First build typically takes 2-4 minutes
  3. Look for successful completion message

Common build issues:

  • Node version error: Cloudflare uses Node 18+ by default (should work)
  • Missing dependencies: Check that package.json is committed
  • Build timeout: Large sites may need optimization

If build succeeds:

  • You’ll see “Success! Your site is live!”
  • Cloudflare provides a URL like https://garage-xxx.pages.dev
  • Click the URL to visit your deployed site

Step 5: Verify Deployment

  1. Visit your *.pages.dev URL
  2. Check that:
    • Homepage loads correctly
    • Navigation works
    • Links resolve properly
    • Images display
    • Search functions
    • Graph view appears

Step 5.1: Verify Privacy Settings

Critical: Verify sensitive content is properly excluded from production build.

  1. Check build logs:

    • Look for: Building for: PRODUCTION (Cloudflare)
    • Look for: Ignore patterns: 11 total (not 8)
  2. Verify excluded content returns 404:

    • Try: https://your-site.pages.dev/Email-Imports/ → Should be 404
    • Try: https://your-site.pages.dev/10-Planning/Budget-&-Financing → Should be 404
    • Try: https://your-site.pages.dev/30-Vendors-&-Contacts/ → Should be 404
  3. Verify public content works:

    • Check: Design documents load
    • Check: Meeting notes load
    • Check: Build progress pages load
    • Check: Photo gallery works

If sensitive content is accessible: Check that Cloudflare is setting NODE_ENV=production (it should by default).

Note: At this point, your site is publicly accessible but with sensitive content excluded. We’ll add authentication next.

Platform Limits and Constraints

Cloudflare Pages has the following limits that you should be aware of:

File Quantity Limit

  • Maximum files: 20,000 files per deployment
  • This includes all files in your public/ output directory
  • For typical documentation sites with images, you’d need thousands of images to hit this limit

Individual File Size Limit

  • Maximum file size: 25 MiB per file
  • Applies to all static assets (images, PDFs, videos, etc.)
  • Most optimized web images are well under this limit (typically 200KB - 2MB)

Build Limits

  • Build timeout: 20 minutes maximum
  • Monthly builds: 500 builds per month (Free plan)
  • Note: There is no explicit total deployment size limit

Practical Implications

For image-heavy sites:

  • Optimize images before uploading (resize to 1920px width max, compress)
  • Use web-friendly formats (JPEG for photos, WebP for better compression)
  • With typical optimized images (500KB - 1MB each), you can easily host thousands of photos
  • Example: 5,000 images at 1MB each = 5GB total (no problem!)

If you hit the limits:

  1. File count exceeded (20,000 files):

    • Use ignorePatterns in quartz.config.ts to exclude archive folders
    • Consider archiving old content to a separate repository
    • Use Cloudflare R2 for excess assets (see below)
  2. Individual file too large (>25 MiB):

    • Compress large videos before uploading
    • For files over 25 MiB, use Cloudflare R2 storage:
  3. Build timeout (>20 minutes):

    • Optimize your build process
    • Reduce number of files being processed
    • Check for infinite loops or performance issues in build

Bottom line: For a typical construction documentation site with hundreds or even thousands of optimized images, you won’t hit any limits. The 20,000 file count and 25 MiB per-file limits are quite generous for normal use cases.


Part 2: Cloudflare Access Authentication

Cloudflare Access creates a login page before your site, ensuring only approved users can view it.

Step 1: Enable Cloudflare Zero Trust

  1. In Cloudflare Dashboard, click Zero Trust in left sidebar
  2. If first time:
    • Click Get started
    • Choose a team name (e.g., “garage-project” or your name)
    • Click Continue
  3. You’re now in the Zero Trust dashboard

Step 2: Configure Identity Providers

Choose how users will authenticate. You can enable multiple options.

  1. Navigate to SettingsAuthentication
  2. Under Login methods, click Add new under “Identity providers”

Recommended options:

Option A: One-time PIN (Email OTP)

Best for: Family/friends who don’t want to create accounts

  1. Click Add under “One-time PIN”
  2. No additional configuration needed
  3. Click Save

How it works:

  • User enters their email
  • Receives a 6-digit code via email
  • Enters code to access site
  • Session lasts 24 hours (configurable)

Option B: Google OAuth

Best for: Users with Gmail accounts

  1. Click Add under “Google”
  2. Leave default settings (uses Cloudflare’s Google OAuth app)
  3. Click Save

How it works:

  • User clicks “Sign in with Google”
  • Authenticates with Google account
  • Redirected to your site

Option C: GitHub OAuth

Best for: Technical users with GitHub accounts

  1. Click Add under “GitHub”
  2. Leave default settings
  3. Click Save

Recommendation: Enable One-time PIN at minimum. You can enable multiple methods.

Step 3: Create Access Application and Policy

Now protect your site with an access policy. Important: The application and policy are created together in a single workflow.

  1. Navigate to AccessApplications
  2. Click Add an application
  3. Select Self-hosted

Step 3.1: Configure Application

FieldValueDescription
Application nameGarage Construction DocsFriendly name (users will see this)
Session Duration24 hoursHow long users stay logged in
Application domaingarage-xxx.pages.devYour Cloudflare Pages URL (no https://)
Subdomain(leave blank)
Path/Protect entire site
  1. Click Next

Step 3.2: Add Policy (Critical Step!)

This is where you define WHO can access your site. Don’t skip this step or users won’t be able to log in.

Policy configuration:

FieldValue
Policy nameApproved Viewers
ActionAllow
Session durationSame as application

Add an Include rule:

  1. Click Add include
  2. Select Emails
  3. Enter email addresses of approved users (one per line):
    your-email@gmail.com
    friend@example.com
    family@yahoo.com
    
    Important: Enter at least YOUR email address here for testing!

Alternative: Use email domain: If you want to allow all users from a specific domain:

  1. Select Emails ending in

  2. Enter domain: @yourdomain.com

  3. Click Next

Step 3.3: Additional Settings (Optional)

CORS Settings: Leave default

Cookie Settings: Leave default

Additional settings:

  • Enable automatic cloudflared authentication: No (leave unchecked)
  • Enable App Launcher: Yes (optional - shows app in user dashboard)
  • Custom logo: Upload garage/project logo (optional)
  1. Click Add application

Step 4: Verify Policy is Attached

CRITICAL: After creating the application, verify the policy is properly attached. This is a common issue that prevents login.

  1. Navigate to AccessPolicies
  2. Find your “Approved Viewers” policy
  3. Check the “Used by applications” field
  4. It should show: Garage Construction Docs (your application name)
  5. If it shows blank (—) or nothing:
    • Your policy is NOT attached to the application
    • Go to AccessApplications
    • Click on your application
    • Click Edit
    • Look for the Policies section
    • Ensure “Approved Viewers” is listed and enabled
    • Click Save application

Visual verification:

  • Correct: Used by applications: Garage Construction Docs
  • Wrong: Used by applications: -- (blank)

If the policy shows as blank, authentication will not work and you won’t receive OTP emails!

Step 5: Test Authentication

Critical: Test in incognito/private browsing mode

  1. Open incognito/private window
  2. Visit your site: https://garage-xxx.pages.dev
  3. You should see Cloudflare Access login page
  4. Test authentication flow:

For One-time PIN:

  1. Enter an approved email address
  2. Check email for 6-digit code
  3. Enter code
  4. Should be redirected to your site

For Google/GitHub:

  1. Click “Sign in with Google” or GitHub
  2. Authenticate with that service
  3. Should be redirected to your site

Test blocked access:

  1. Try with email NOT on approved list
  2. Should see “Access Denied” message

Test session duration:

  1. Close browser
  2. Reopen and visit site within 24 hours
  3. Should still be logged in (no re-authentication needed)

Part 3: Managing Users

Adding New Users

  1. Navigate to AccessApplications
  2. Click on your application (Garage Construction Docs)
  3. Click Edit on your policy
  4. Add email addresses to the Include rule
  5. Click Save

Users receive access immediately - no need to redeploy site.

Removing Users

  1. Edit the policy as above
  2. Remove email address from list
  3. Click Save

User’s existing session will expire after the session duration (24 hours default).

To immediately revoke access:

  1. Navigate to AccessUsers
  2. Find user in list
  3. Click Revoke next to their session

Viewing Access Logs

Monitor who’s accessing your site:

  1. Navigate to LogsAccess
  2. View authentication attempts:
    • Successful logins
    • Blocked attempts
    • User information
    • Timestamp and location

Useful for:

  • Confirming users can access site
  • Identifying unauthorized access attempts
  • Troubleshooting login issues

Part 4: Custom Domain (Optional)

Use your own domain instead of *.pages.dev.

Prerequisites

Option A: Domain on Cloudflare

  • Add your domain to Cloudflare (free)
  • Update nameservers with registrar

Option B: Domain elsewhere

  • Create CNAME record pointing to your Pages site

Setup with Cloudflare-managed Domain

  1. Add custom domain:

    • Go to Workers & Pages → Your project
    • Click Custom domains tab
    • Click Set up a custom domain
    • Enter your domain: garage.yourdomain.com
    • Click Continue
  2. DNS automatically configured:

    • Cloudflare creates CNAME record
    • SSL certificate auto-provisioned
    • Takes 1-2 minutes
  3. Update Access application:

    • Go to AccessApplications
    • Edit your application
    • Change Application domain to garage.yourdomain.com
    • Click Save
  4. Update Quartz config:

    • Edit quartz.config.ts
    • Update baseUrl: "garage.yourdomain.com"
    • Commit and push (triggers rebuild)

Setup with External Domain

  1. Get Pages URL:

    • In your Pages project, note the target: garage-xxx.pages.dev
  2. Create CNAME record at registrar:

    • Name: garage (or desired subdomain)
    • Type: CNAME
    • Value: garage-xxx.pages.dev
    • TTL: Auto or 3600
  3. Add domain in Cloudflare Pages:

    • Custom domains → Add domain
    • Cloudflare verifies DNS record
    • Provisions SSL certificate
  4. Update Access and Quartz config (same as above)


Part 5: Automatic Deployments

How It Works

Once set up, deployments are automatic:

  1. You: Edit markdown files in Obsidian
  2. You: Commit and push to GitHub
  3. Cloudflare: Detects new commit
  4. Cloudflare: Runs build automatically
  5. Cloudflare: Deploys to production
  6. Users: See updated content (2-3 minutes)

Monitoring Deployments

  1. Go to Workers & Pages → Your project
  2. Click Deployments tab
  3. See deployment history:
    • Build status (success/failure)
    • Commit message
    • Timestamp
    • Build logs

Branch Deployments

Cloudflare creates preview deployments for all branches:

  • Production branch (e.g., main): Deploys to main URL
  • Other branches (e.g., feature/photos): Deploy to preview URLs
    • Preview URL: feature-photos.garage-xxx.pages.dev
    • Useful for testing before merging

Access protection:

  • Production site: Protected by Cloudflare Access
  • Preview deployments: Also protected (same policy applies)

Skipping Deployments

To push commits without triggering deployment:

Add [skip ci] to commit message:

git commit -m "docs: update planning notes [skip ci]"

Part 6: Maintenance and Updates

Regular Content Updates

Workflow:

  1. Edit files in Obsidian
  2. Commit changes:
    git add .
    git commit -m "docs: add progress photos from Oct 25"
    git push
  3. Wait 2-3 minutes for deployment
  4. Visit site to verify

Quartz Updates

Update Quartz when new versions release:

# Check current version
npm list quartz
 
# Update to latest
npm update quartz
 
# Test locally
npx quartz build --serve
 
# Commit and push
git add package.json package-lock.json
git commit -m "chore: update Quartz to latest version"
git push

Configuration Changes

When modifying quartz.config.ts or quartz.layout.ts:

  1. Test locally first:
    npx quartz build --serve
  2. Verify changes work correctly
  3. Commit and push
  4. Monitor deployment logs for errors

Rollback Failed Deployment

If deployment fails or introduces issues:

  1. Go to Workers & Pages → Your project → Deployments
  2. Find last successful deployment
  3. Click (three dots) → Rollback to this deployment
  4. Fix issue locally
  5. Deploy corrected version

Part 7: Troubleshooting

Authentication Issues

Problem: Not receiving One-time PIN emails ⚠️ Most Common Issue

This usually means the policy is not attached to the application.

Check:

  1. Navigate to AccessPolicies
  2. Find your policy
  3. Check the “Used by applications” field
  4. If it shows blank (—):
    • Go to AccessApplications
    • Click your application → Edit
    • Find the Policies section
    • Attach your policy to the application
    • Click Save application
  5. Also check:
    • Email spam/junk folder (Gmail often filters these)
    • One-time PIN is enabled in Settings → Authentication
    • Email address matches exactly (no typos, spaces)

Solution:

  • Attach the policy to your application (most common fix)
  • Check spam folder
  • Add no-reply@cloudflareaccess.com to contacts
  • Try alternative authentication method (Google OAuth)

Problem: “Access Denied” for approved user

Check:

  1. Policy is attached to application (see above)
  2. Email address is in approved list (exact match, including capitalization)
  3. Application domain matches actual URL
  4. Path is set to / (not /path)
  5. Policy action is “Allow” (not “Block”)

Solution:

  • Verify policy attachment (Access → Policies → check “Used by applications”)
  • Edit application, verify domain
  • Edit policy, verify email is in Include rule

Build Failures

Problem: Build fails on Cloudflare but works locally

Check build logs for error message.

Common causes:

  1. Missing files in git:

    git status  # Check for untracked files
    git add .
  2. Node version mismatch:

    • Set Node version in Pages settings
    • Environment variables: NODE_VERSION=20
  3. Memory issues:

    • Optimize large images
    • Exclude large folders in quartz.config.ts

Problem: Build succeeds but site looks wrong

Check:

  1. baseUrl in quartz.config.ts matches deployment URL
  2. Image paths are correct (relative to content/)
  3. No absolute paths like /Users/...

Site Access Issues

Problem: Site works but then stops working

Check:

  1. Cloudflare status: https://www.cloudflarestatus.com/
  2. DNS settings (if using custom domain)
  3. Access policy wasn’t accidentally changed

Problem: Some pages don’t load

Check:

  1. File names don’t have special characters
  2. Wiki-links resolve correctly
  3. Check browser console for errors (F12)

Performance Issues

Problem: Site loads slowly

Solutions:

  1. Optimize images:

    • Resize to max 1920px width
    • Compress with tools like TinyPNG
    • Use JPEG for photos, PNG for diagrams
  2. Exclude large folders:

    // quartz.config.ts
    ignorePatterns: [
      "archive",
      "old-photos",
    ]
  3. Use Cloudflare’s CDN:

    • Already active by default
    • Content cached globally
    • Should be very fast

Part 8: Sharing Your Site

Sending Invitations

Email template for new users:

Subject: Invitation: Garage Construction Documentation

Hi [Name],

I'd like to share documentation of my garage construction project with you!

Site URL: https://garage.yourdomain.com
(or https://garage-xxx.pages.dev)

HOW TO ACCESS:
1. Click the link above
2. Enter your email address: [their-email@example.com]
3. Check your email for a 6-digit code
4. Enter the code to access the site

The site includes:
- Progress photos and drone footage
- Construction timeline and milestones
- Design decisions and specifications
- Meeting notes and contractor communications

Once you log in, you'll stay logged in for 24 hours. After that, just enter your email again for a new code.

Feel free to explore and let me know what you think!

[Your name]

Promoting Your Site

Share on:

  • Email to family/friends
  • Private Facebook group
  • Text message with link

What to share:

  • Highlight recent updates (new photos, milestones)
  • Specific pages of interest
  • Invite feedback via Giscus comments (if enabled)

Monitoring Usage

View statistics:

  1. Cloudflare Dashboard → Analytics → Your site
  2. See:
    • Total visits
    • Geographic distribution
    • Peak usage times
    • Bandwidth used

Access logs:

  1. Access → Users
  2. See who’s logged in recently

Quick Reference

Essential URLs

ResourceURL
Cloudflare Dashboardhttps://dash.cloudflare.com
Zero Trust Dashboardhttps://one.dash.cloudflare.com
Your site (production)https://garage-xxx.pages.dev
Your site (custom domain)https://garage.yourdomain.com
Build logsWorkers & Pages → Project → Deployments

Common Commands

# Local preview
npx quartz build --serve
 
# Build only (no server)
npx quartz build
 
# Update dependencies
npm update
 
# Check Quartz version
npm list quartz

Support Resources


Security Best Practices

Email Management

  1. Don’t add generic emails:

    • Avoid: info@company.com, admin@domain.com
    • Use specific person’s email
  2. Review access list regularly:

    • Remove users who no longer need access
    • Update when people change email addresses
  3. Monitor access logs:

    • Check weekly for unusual activity
    • Investigate failed login attempts

Site Security

  1. Keep dependencies updated:

    • Update Quartz monthly
    • Check npm audit for vulnerabilities
  2. Protect sensitive info:

    • Use ignorePatterns for private content
    • Don’t commit sensitive data to git
    • Review PII Audit Report recommendations
  3. Use HTTPS only:

    • Cloudflare enforces this automatically
    • Never disable SSL

Access Policy Best Practices

  1. Use specific emails:

    • Individual emails better than domain wildcards
    • Easier to track and revoke
  2. Set appropriate session duration:

    • 24 hours good for casual use
    • Shorter (4 hours) for more security
  3. Enable multiple auth methods:

    • Gives users flexibility
    • Backup if one method fails

Next Steps

Completed ✅

  1. Test the site thoroughly:

    • Verify all pages load
    • Test authentication (One-time PIN working)
    • Verify privacy settings (sensitive content excluded)
    • Test automated deployments (CI/CD working)
  2. Initial setup:

    • Site deployed to https://e3059a95.garage-build.pages.dev/
    • Cloudflare Access configured
    • Your email added to access policy
  3. Set up custom domain:

    • Configure DNS for garage.gahagan.cloud
    • Update Cloudflare Access application domain
    • Update quartz.config.ts baseUrl
    • Test custom domain access
    • ✅ Site live at: https://garage.gahagan.cloud/

Remaining Tasks

  1. Add additional users:

    • Create list of approved emails
    • Add emails to Access policy
    • Send invitation emails to users
    • Test with real users
  2. Test additional features:

    • Check mobile experience
    • Verify images and videos work
    • Test all navigation and links
    • Review search functionality
  3. Monitor first week:

    • Check access logs daily
    • Review build status after updates
    • Gather user feedback
    • Address any issues
  4. Consider enhancements:

    • Enable Giscus comments (Giscus Setup Guide)
    • Add analytics (Google Analytics or Plausible)
    • Create navigation improvements
    • Optimize images (see Issues)


This guide will get your garage construction documentation live and accessible to approved users within 1-2 hours. Once deployed, updates are automatic when you push to GitHub.