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 --serveworks - 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
- Go to https://dash.cloudflare.com/sign-up
- Create account with email and password
- Verify your email address
- Log in to Cloudflare Dashboard
Step 2: Connect GitHub Repository
- In Cloudflare Dashboard, navigate to Workers & Pages in left sidebar
- Click Create application button
- Select Pages tab
- Click Connect to Git
- Click Connect GitHub
- 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
- Click Install & Authorize
Step 3: Configure Build Settings
After connecting GitHub:
- Select repository: Choose
dgahagan/garage-build(or your repository name) - Select branch: Choose
feature/web-presentation(ormainif merged) - Click Begin setup
Configure the build:
| Setting | Value |
|---|---|
| Project name | garage (or your preferred name - this becomes part of the URL) |
| Production branch | main or feature/web-presentation |
| Framework preset | None (leave as “None”) |
| Build command | npx quartz build |
| Build output directory | public |
| Root directory | / (leave blank) |
Environment variables: None needed for basic setup
- Click Save and Deploy
Step 4: Monitor First Build
- Watch the build logs in real-time
- First build typically takes 2-4 minutes
- Look for successful completion message
Common build issues:
- Node version error: Cloudflare uses Node 18+ by default (should work)
- Missing dependencies: Check that
package.jsonis 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
- Visit your
*.pages.devURL - 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.
-
Check build logs:
- Look for:
Building for: PRODUCTION (Cloudflare) - Look for:
Ignore patterns: 11 total(not 8)
- Look for:
-
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
- Try:
-
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:
-
File count exceeded (20,000 files):
- Use
ignorePatternsinquartz.config.tsto exclude archive folders - Consider archiving old content to a separate repository
- Use Cloudflare R2 for excess assets (see below)
- Use
-
Individual file too large (>25 MiB):
- Compress large videos before uploading
- For files over 25 MiB, use Cloudflare R2 storage:
- Upload files to R2 bucket
- Enable public access on bucket
- Link to files from your Pages site
- See: https://developers.cloudflare.com/r2/
-
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
- In Cloudflare Dashboard, click Zero Trust in left sidebar
- If first time:
- Click Get started
- Choose a team name (e.g., “garage-project” or your name)
- Click Continue
- You’re now in the Zero Trust dashboard
Step 2: Configure Identity Providers
Choose how users will authenticate. You can enable multiple options.
- Navigate to Settings → Authentication
- 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
- Click Add under “One-time PIN”
- No additional configuration needed
- 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
- Click Add under “Google”
- Leave default settings (uses Cloudflare’s Google OAuth app)
- 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
- Click Add under “GitHub”
- Leave default settings
- 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.
- Navigate to Access → Applications
- Click Add an application
- Select Self-hosted
Step 3.1: Configure Application
| Field | Value | Description |
|---|---|---|
| Application name | Garage Construction Docs | Friendly name (users will see this) |
| Session Duration | 24 hours | How long users stay logged in |
| Application domain | garage-xxx.pages.dev | Your Cloudflare Pages URL (no https://) |
| Subdomain | (leave blank) | |
| Path | / | Protect entire site |
- 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:
| Field | Value |
|---|---|
| Policy name | Approved Viewers |
| Action | Allow |
| Session duration | Same as application |
Add an Include rule:
- Click Add include
- Select Emails
- Enter email addresses of approved users (one per line):
Important: Enter at least YOUR email address here for testing!your-email@gmail.com friend@example.com family@yahoo.com
Alternative: Use email domain: If you want to allow all users from a specific domain:
-
Select Emails ending in
-
Enter domain:
@yourdomain.com -
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)
- 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.
- Navigate to Access → Policies
- Find your “Approved Viewers” policy
- Check the “Used by applications” field
- It should show:
Garage Construction Docs(your application name) - If it shows blank (—) or nothing:
- Your policy is NOT attached to the application
- Go to Access → Applications
- 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
- Open incognito/private window
- Visit your site:
https://garage-xxx.pages.dev - You should see Cloudflare Access login page
- Test authentication flow:
For One-time PIN:
- Enter an approved email address
- Check email for 6-digit code
- Enter code
- Should be redirected to your site
For Google/GitHub:
- Click “Sign in with Google” or GitHub
- Authenticate with that service
- Should be redirected to your site
Test blocked access:
- Try with email NOT on approved list
- Should see “Access Denied” message
Test session duration:
- Close browser
- Reopen and visit site within 24 hours
- Should still be logged in (no re-authentication needed)
Part 3: Managing Users
Adding New Users
- Navigate to Access → Applications
- Click on your application (
Garage Construction Docs) - Click Edit on your policy
- Add email addresses to the Include rule
- Click Save
Users receive access immediately - no need to redeploy site.
Removing Users
- Edit the policy as above
- Remove email address from list
- Click Save
User’s existing session will expire after the session duration (24 hours default).
To immediately revoke access:
- Navigate to Access → Users
- Find user in list
- Click Revoke next to their session
Viewing Access Logs
Monitor who’s accessing your site:
- Navigate to Logs → Access
- 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
-
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
-
DNS automatically configured:
- Cloudflare creates CNAME record
- SSL certificate auto-provisioned
- Takes 1-2 minutes
-
Update Access application:
- Go to Access → Applications
- Edit your application
- Change Application domain to
garage.yourdomain.com - Click Save
-
Update Quartz config:
- Edit
quartz.config.ts - Update
baseUrl: "garage.yourdomain.com" - Commit and push (triggers rebuild)
- Edit
Setup with External Domain
-
Get Pages URL:
- In your Pages project, note the target:
garage-xxx.pages.dev
- In your Pages project, note the target:
-
Create CNAME record at registrar:
- Name:
garage(or desired subdomain) - Type:
CNAME - Value:
garage-xxx.pages.dev - TTL:
Autoor3600
- Name:
-
Add domain in Cloudflare Pages:
- Custom domains → Add domain
- Cloudflare verifies DNS record
- Provisions SSL certificate
-
Update Access and Quartz config (same as above)
Part 5: Automatic Deployments
How It Works
Once set up, deployments are automatic:
- You: Edit markdown files in Obsidian
- You: Commit and push to GitHub
- Cloudflare: Detects new commit
- Cloudflare: Runs build automatically
- Cloudflare: Deploys to production
- Users: See updated content (2-3 minutes)
Monitoring Deployments
- Go to Workers & Pages → Your project
- Click Deployments tab
- 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
- Preview URL:
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:
- Edit files in Obsidian
- Commit changes:
git add . git commit -m "docs: add progress photos from Oct 25" git push - Wait 2-3 minutes for deployment
- 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 pushConfiguration Changes
When modifying quartz.config.ts or quartz.layout.ts:
- Test locally first:
npx quartz build --serve - Verify changes work correctly
- Commit and push
- Monitor deployment logs for errors
Rollback Failed Deployment
If deployment fails or introduces issues:
- Go to Workers & Pages → Your project → Deployments
- Find last successful deployment
- Click ⋮ (three dots) → Rollback to this deployment
- Fix issue locally
- 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:
- Navigate to Access → Policies
- Find your policy
- Check the “Used by applications” field
- If it shows blank (—):
- Go to Access → Applications
- Click your application → Edit
- Find the Policies section
- Attach your policy to the application
- Click Save application
- 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.comto contacts - Try alternative authentication method (Google OAuth)
Problem: “Access Denied” for approved user
Check:
- Policy is attached to application (see above)
- Email address is in approved list (exact match, including capitalization)
- Application domain matches actual URL
- Path is set to
/(not/path) - 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:
-
Missing files in git:
git status # Check for untracked files git add . -
Node version mismatch:
- Set Node version in Pages settings
- Environment variables:
NODE_VERSION=20
-
Memory issues:
- Optimize large images
- Exclude large folders in
quartz.config.ts
Problem: Build succeeds but site looks wrong
Check:
baseUrlinquartz.config.tsmatches deployment URL- Image paths are correct (relative to
content/) - No absolute paths like
/Users/...
Site Access Issues
Problem: Site works but then stops working
Check:
- Cloudflare status: https://www.cloudflarestatus.com/
- DNS settings (if using custom domain)
- Access policy wasn’t accidentally changed
Problem: Some pages don’t load
Check:
- File names don’t have special characters
- Wiki-links resolve correctly
- Check browser console for errors (F12)
Performance Issues
Problem: Site loads slowly
Solutions:
-
Optimize images:
- Resize to max 1920px width
- Compress with tools like TinyPNG
- Use JPEG for photos, PNG for diagrams
-
Exclude large folders:
// quartz.config.ts ignorePatterns: [ "archive", "old-photos", ] -
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:
- Cloudflare Dashboard → Analytics → Your site
- See:
- Total visits
- Geographic distribution
- Peak usage times
- Bandwidth used
Access logs:
- Access → Users
- See who’s logged in recently
Quick Reference
Essential URLs
| Resource | URL |
|---|---|
| Cloudflare Dashboard | https://dash.cloudflare.com |
| Zero Trust Dashboard | https://one.dash.cloudflare.com |
| Your site (production) | https://garage-xxx.pages.dev |
| Your site (custom domain) | https://garage.yourdomain.com |
| Build logs | Workers & 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 quartzSupport Resources
- Cloudflare Pages Docs: https://developers.cloudflare.com/pages/
- Cloudflare Access Docs: https://developers.cloudflare.com/cloudflare-one/
- Quartz Documentation: https://quartz.jzhao.xyz/
- Community support: Cloudflare Discord, Quartz GitHub Discussions
Security Best Practices
Email Management
-
Don’t add generic emails:
- Avoid:
info@company.com,admin@domain.com - Use specific person’s email
- Avoid:
-
Review access list regularly:
- Remove users who no longer need access
- Update when people change email addresses
-
Monitor access logs:
- Check weekly for unusual activity
- Investigate failed login attempts
Site Security
-
Keep dependencies updated:
- Update Quartz monthly
- Check
npm auditfor vulnerabilities
-
Protect sensitive info:
- Use
ignorePatternsfor private content - Don’t commit sensitive data to git
- Review PII Audit Report recommendations
- Use
-
Use HTTPS only:
- Cloudflare enforces this automatically
- Never disable SSL
Access Policy Best Practices
-
Use specific emails:
- Individual emails better than domain wildcards
- Easier to track and revoke
-
Set appropriate session duration:
- 24 hours good for casual use
- Shorter (4 hours) for more security
-
Enable multiple auth methods:
- Gives users flexibility
- Backup if one method fails
Next Steps
Completed ✅
-
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)
-
Initial setup:
- Site deployed to
https://e3059a95.garage-build.pages.dev/ - Cloudflare Access configured
- Your email added to access policy
- Site deployed to
-
Set up custom domain:
- Configure DNS for
garage.gahagan.cloud - Update Cloudflare Access application domain
- Update
quartz.config.tsbaseUrl - Test custom domain access
- ✅ Site live at:
https://garage.gahagan.cloud/
- Configure DNS for
Remaining Tasks
-
Add additional users:
- Create list of approved emails
- Add emails to Access policy
- Send invitation emails to users
- Test with real users
-
Test additional features:
- Check mobile experience
- Verify images and videos work
- Test all navigation and links
- Review search functionality
-
Monitor first week:
- Check access logs daily
- Review build status after updates
- Gather user feedback
- Address any issues
-
Consider enhancements:
- Enable Giscus comments (Giscus Setup Guide)
- Add analytics (Google Analytics or Plausible)
- Create navigation improvements
- Optimize images (see Issues)
Related Documentation
- 03 - Web Publishing with Quartz - Quartz setup and configuration
- 04 - Documentation Workflows - Content creation workflows
- Web Presentation Implementation Plan - Original planning document
- PII Audit Report - Privacy and security considerations
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.