Spike: Custom domain routing for palinks.app #25

Merged
ldraney merged 2 commits from 15-custom-domain-spike into main 2026-06-08 03:16:41 +00:00
Owner

Summary

  • Research spike documenting how to route palinks.app to production
  • Tailscale Funnel cannot serve custom domains (locked to *.ts.net), so two alternatives evaluated: GoDaddy 301 redirect vs Cloudflare Tunnel as canonical URL
  • Recommends phased approach: 301 redirect now, Cloudflare Tunnel after Keycloak integration stabilizes

Changes

  • docs/custom-domain.md: new spike document with research findings, cost/complexity comparison table, mermaid diagrams for both network paths (redirect and canonical), phased recommendation, and follow-up ticket descriptions

Test Plan

  • Review findings in docs/custom-domain.md for accuracy against current infrastructure
  • Confirm phased recommendation aligns with Keycloak integration timeline (issue #16)
  • No code changes to test -- documentation-only spike

Review Checklist

  • Passed automated review-fix loop
  • No secrets committed
  • No unnecessary file changes
  • Commit messages are descriptive
  • Feature flag needed? No -- documentation-only spike, no code changes
  • ldraney/palinks #15 -- Spike: Route palinks.app domain to production
  • palinks -- the project this work belongs to

Closes #15

## Summary - Research spike documenting how to route `palinks.app` to production - Tailscale Funnel cannot serve custom domains (locked to `*.ts.net`), so two alternatives evaluated: GoDaddy 301 redirect vs Cloudflare Tunnel as canonical URL - Recommends phased approach: 301 redirect now, Cloudflare Tunnel after Keycloak integration stabilizes ## Changes - `docs/custom-domain.md`: new spike document with research findings, cost/complexity comparison table, mermaid diagrams for both network paths (redirect and canonical), phased recommendation, and follow-up ticket descriptions ## Test Plan - [ ] Review findings in `docs/custom-domain.md` for accuracy against current infrastructure - [ ] Confirm phased recommendation aligns with Keycloak integration timeline (issue #16) - [ ] No code changes to test -- documentation-only spike ## Review Checklist - [x] Passed automated review-fix loop - [x] No secrets committed - [x] No unnecessary file changes - [x] Commit messages are descriptive - [x] Feature flag needed? No -- documentation-only spike, no code changes ## Related Notes - `ldraney/palinks #15` -- Spike: Route palinks.app domain to production - `palinks` -- the project this work belongs to Closes #15
Research Tailscale Funnel limitations (locked to *.ts.net), evaluate
GoDaddy 301 redirect vs Cloudflare Tunnel as canonical, and recommend
a phased approach: redirect now, Cloudflare Tunnel after Keycloak.

Closes #15

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author
Owner

QA Review -- PR #25

Scope: Documentation-only spike. Single new file docs/custom-domain.md (163 lines added, 0 deleted).

Findings

No blocking issues found.

  1. Research quality: strong. All four spike questions are answered with citations. The Tailscale Funnel limitation is correctly identified with a link to the upstream FR (tailscale/tailscale#11563) and the official docs. Cloudflare Tunnel recommendation is well-reasoned for this stack.

  2. Phased recommendation is sound. Avoiding coupling the domain migration with the Keycloak integration (#16) is the right call -- changing redirect URIs twice would be unnecessary churn.

  3. Mermaid diagrams are correct. Both Phase 1 (redirect) and Phase 2 (canonical) network paths accurately reflect the architecture described in docs/infrastructure.md. The Phase 2 diagram correctly shows Cloudflare Tunnel and Tailscale Funnel coexisting on the same k8s Service.

  4. Follow-up tickets are well-scoped. Effort estimates are reasonable. The ticket descriptions have enough detail for implementation without over-specifying.

  5. Minor note (non-blocking): The comparison table lists "Maintenance burden: Zero" for the GoDaddy redirect. This is accurate for the redirect itself, but worth noting that GoDaddy domain renewal is an ongoing cost (~$20/yr for .app domains). Not a doc fix needed -- just context for the reader.

  6. Consistency with existing docs: The document correctly references the same architecture (Puma on port 80, Tailscale Funnel TLS termination, k8s Service) as docs/infrastructure.md and docs/architecture.md. No contradictions.

SOP Compliance

  • PR body follows template (Summary, Changes, Test Plan, Review Checklist, Related Notes)
  • Closes #15 present
  • Branch naming follows convention (15-custom-domain-spike)
  • No secrets committed
  • Commit message is descriptive
  • Feature flag consideration addressed (N/A for docs-only)

VERDICT: APPROVE

## QA Review -- PR #25 **Scope:** Documentation-only spike. Single new file `docs/custom-domain.md` (163 lines added, 0 deleted). ### Findings **No blocking issues found.** 1. **Research quality: strong.** All four spike questions are answered with citations. The Tailscale Funnel limitation is correctly identified with a link to the upstream FR (tailscale/tailscale#11563) and the official docs. Cloudflare Tunnel recommendation is well-reasoned for this stack. 2. **Phased recommendation is sound.** Avoiding coupling the domain migration with the Keycloak integration (#16) is the right call -- changing redirect URIs twice would be unnecessary churn. 3. **Mermaid diagrams are correct.** Both Phase 1 (redirect) and Phase 2 (canonical) network paths accurately reflect the architecture described in `docs/infrastructure.md`. The Phase 2 diagram correctly shows Cloudflare Tunnel and Tailscale Funnel coexisting on the same k8s Service. 4. **Follow-up tickets are well-scoped.** Effort estimates are reasonable. The ticket descriptions have enough detail for implementation without over-specifying. 5. **Minor note (non-blocking):** The comparison table lists "Maintenance burden: Zero" for the GoDaddy redirect. This is accurate for the redirect itself, but worth noting that GoDaddy domain renewal is an ongoing cost (~$20/yr for `.app` domains). Not a doc fix needed -- just context for the reader. 6. **Consistency with existing docs:** The document correctly references the same architecture (Puma on port 80, Tailscale Funnel TLS termination, k8s Service) as `docs/infrastructure.md` and `docs/architecture.md`. No contradictions. ### SOP Compliance - [x] PR body follows template (Summary, Changes, Test Plan, Review Checklist, Related Notes) - [x] `Closes #15` present - [x] Branch naming follows convention (`15-custom-domain-spike`) - [x] No secrets committed - [x] Commit message is descriptive - [x] Feature flag consideration addressed (N/A for docs-only) **VERDICT: APPROVE**
Author
Owner

PR #25 Review

DOMAIN REVIEW

Tech stack: Documentation-only spike (Markdown). No application code changes. The document covers infrastructure topics (Tailscale Funnel, Cloudflare Tunnel, k8s ingress, DNS, TLS termination) so infrastructure domain expertise applies to content accuracy.

Content quality assessment:

The spike document is thorough and well-structured. Specific strengths:

  1. Question-driven format -- each research question is clearly framed with a definitive answer. The Tailscale Funnel limitation is correctly identified (locked to *.ts.net, TLS cert mismatch on CNAME).
  2. Cost/complexity comparison table -- side-by-side evaluation of both options with concrete factors. Monthly cost, setup time, reversibility, and downstream impact (Keycloak, Rails) are all covered.
  3. Mermaid diagrams -- both Phase 1 (redirect) and Phase 2 (canonical) network paths are diagrammed. These are consistent with the existing diagrams in docs/architecture.md and docs/infrastructure.md.
  4. Follow-up tickets -- four concrete tickets with effort estimates and per-repo scope. The phased approach correctly sequences Phase 2 after Keycloak integration (issue #16).
  5. Sources -- six external references cited, including the specific Tailscale GitHub issue and Cloudflare deployment docs.

Cross-reference with existing docs:

  • docs/infrastructure.md line 168-169 already references this spike ("see Spike #15"), so the docs are internally consistent.
  • The existing infrastructure doc correctly shows Tailscale Funnel as the sole ingress path. The spike document's Phase 2 diagram correctly shows Cloudflare Tunnel coexisting alongside Funnel -- this is architecturally sound since both terminate TLS independently and forward to the same k8s Service.
  • The TLS chain description (User -> Cloudflare Edge -> Cloudflare Tunnel -> cloudflared pod -> k8s Service -> Rails Pod) is accurate for a Cloudflare Tunnel deployment.

One technical observation (non-blocking): The spike recommends transferring DNS nameservers to Cloudflare while keeping GoDaddy as registrar. This is correct and standard practice. However, the Phase 1 GoDaddy 301 redirect will need to be removed/reconfigured when Phase 2 transfers DNS to Cloudflare, since domain forwarding is handled at the DNS level. This transition is implicitly covered by follow-up ticket #2 but could be called out more explicitly.

BLOCKERS

None. This is a documentation-only spike with no code changes. The standard blocker criteria (test coverage, input validation, secrets, DRY violations) do not apply to documentation PRs.

NITS

  1. README not updated -- The README's Docs section does not list docs/custom-domain.md. Consider adding it alongside the other doc links for discoverability. Example: - [Custom Domain](docs/custom-domain.md) -- routing palinks.app to production

  2. Ingress line in README -- The README Tech Stack section says Ingress: Tailscale funnel (TLS termination). Once Phase 2 lands, this will need updating. Not blocking for this PR since the spike is research, not implementation.

  3. Minor wording -- "What started as a personal bookmarks page" in the README vs the spike doc referring to "portfolio site" -- these are consistent, just noting the terminology evolution is tracked.

SOP COMPLIANCE

  • Branch named after issue: 15-custom-domain-spike follows {issue-number}-{kebab-case-purpose} convention
  • PR body follows template: Summary, Changes, Test Plan, Review Checklist, Related Notes all present
  • Related section references parent issue: ldraney/palinks #15 and Closes #15
  • No secrets committed: documentation only, no credentials or tokens in the diff
  • No unnecessary file changes: single file added (docs/custom-domain.md), 163 additions, 0 deletions
  • Commit messages: not visible in diff but PR title is descriptive

PROCESS OBSERVATIONS

  • Spike quality: This is a well-executed spike. The question-answer format, comparison table, phased recommendation, and concrete follow-up tickets are exactly what a spike should produce. The deliverables are actionable.
  • Change failure risk: Zero -- documentation only, no deployment impact.
  • Dependency awareness: The spike correctly identifies the coupling between custom domain routing and Keycloak integration (#16), recommending Phase 2 wait until auth stabilizes. This avoids changing redirect URIs twice.
  • Documentation gap: The README docs index should be updated to include this new document (nit, not blocking).

VERDICT: APPROVED

## PR #25 Review ### DOMAIN REVIEW **Tech stack:** Documentation-only spike (Markdown). No application code changes. The document covers infrastructure topics (Tailscale Funnel, Cloudflare Tunnel, k8s ingress, DNS, TLS termination) so infrastructure domain expertise applies to content accuracy. **Content quality assessment:** The spike document is thorough and well-structured. Specific strengths: 1. **Question-driven format** -- each research question is clearly framed with a definitive answer. The Tailscale Funnel limitation is correctly identified (locked to `*.ts.net`, TLS cert mismatch on CNAME). 2. **Cost/complexity comparison table** -- side-by-side evaluation of both options with concrete factors. Monthly cost, setup time, reversibility, and downstream impact (Keycloak, Rails) are all covered. 3. **Mermaid diagrams** -- both Phase 1 (redirect) and Phase 2 (canonical) network paths are diagrammed. These are consistent with the existing diagrams in `docs/architecture.md` and `docs/infrastructure.md`. 4. **Follow-up tickets** -- four concrete tickets with effort estimates and per-repo scope. The phased approach correctly sequences Phase 2 after Keycloak integration (issue #16). 5. **Sources** -- six external references cited, including the specific Tailscale GitHub issue and Cloudflare deployment docs. **Cross-reference with existing docs:** - `docs/infrastructure.md` line 168-169 already references this spike ("see Spike #15"), so the docs are internally consistent. - The existing infrastructure doc correctly shows Tailscale Funnel as the sole ingress path. The spike document's Phase 2 diagram correctly shows Cloudflare Tunnel coexisting alongside Funnel -- this is architecturally sound since both terminate TLS independently and forward to the same k8s Service. - The TLS chain description (`User -> Cloudflare Edge -> Cloudflare Tunnel -> cloudflared pod -> k8s Service -> Rails Pod`) is accurate for a Cloudflare Tunnel deployment. **One technical observation (non-blocking):** The spike recommends transferring DNS nameservers to Cloudflare while keeping GoDaddy as registrar. This is correct and standard practice. However, the Phase 1 GoDaddy 301 redirect will need to be removed/reconfigured when Phase 2 transfers DNS to Cloudflare, since domain forwarding is handled at the DNS level. This transition is implicitly covered by follow-up ticket #2 but could be called out more explicitly. ### BLOCKERS None. This is a documentation-only spike with no code changes. The standard blocker criteria (test coverage, input validation, secrets, DRY violations) do not apply to documentation PRs. ### NITS 1. **README not updated** -- The README's Docs section does not list `docs/custom-domain.md`. Consider adding it alongside the other doc links for discoverability. Example: `- [Custom Domain](docs/custom-domain.md) -- routing palinks.app to production` 2. **Ingress line in README** -- The README Tech Stack section says `Ingress: Tailscale funnel (TLS termination)`. Once Phase 2 lands, this will need updating. Not blocking for this PR since the spike is research, not implementation. 3. **Minor wording** -- "What started as a personal bookmarks page" in the README vs the spike doc referring to "portfolio site" -- these are consistent, just noting the terminology evolution is tracked. ### SOP COMPLIANCE - [x] Branch named after issue: `15-custom-domain-spike` follows `{issue-number}-{kebab-case-purpose}` convention - [x] PR body follows template: Summary, Changes, Test Plan, Review Checklist, Related Notes all present - [x] Related section references parent issue: `ldraney/palinks #15` and `Closes #15` - [x] No secrets committed: documentation only, no credentials or tokens in the diff - [x] No unnecessary file changes: single file added (`docs/custom-domain.md`), 163 additions, 0 deletions - [x] Commit messages: not visible in diff but PR title is descriptive ### PROCESS OBSERVATIONS - **Spike quality:** This is a well-executed spike. The question-answer format, comparison table, phased recommendation, and concrete follow-up tickets are exactly what a spike should produce. The deliverables are actionable. - **Change failure risk:** Zero -- documentation only, no deployment impact. - **Dependency awareness:** The spike correctly identifies the coupling between custom domain routing and Keycloak integration (#16), recommending Phase 2 wait until auth stabilizes. This avoids changing redirect URIs twice. - **Documentation gap:** The README docs index should be updated to include this new document (nit, not blocking). ### VERDICT: APPROVED
QA review nit: the new docs/custom-domain.md was missing from
the Docs section in README.md. Added after Pipeline since custom
domain routing is part of getting traffic to production.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author
Owner

PR #25 Review

DOMAIN REVIEW

Tech stack: Documentation-only spike (Markdown). No application code changes. The domain expertise applied here is infrastructure/networking (Tailscale, Cloudflare Tunnel, k8s ingress, DNS, TLS) since the spike evaluates infrastructure options.

Content accuracy verified against codebase:

  • The spike correctly identifies that Tailscale Funnel is the current ingress path (confirmed in docs/infrastructure.md and docs/architecture.md)
  • The k8s Service/Pod/Puma architecture described in the mermaid diagrams matches the existing diagrams in docs/infrastructure.md lines 56-78
  • The mention of Keycloak integration (issue #16) and visibility tiers (issue #17) as dependencies is consistent -- both issues exist and are open
  • Cross-repo change matrix (pal-e-platform, pal-e-services, pal-e-deployments, palinks) aligns with the actual infrastructure layout documented in docs/infrastructure.md
  • The claim that config.hosts needs updating is correct per Rails security defaults
  • References to CNPG, Harbor, ArgoCD, and SOPS-encrypted secrets match the documented stack

Spike quality assessment:

  • Research question is clearly framed (can Tailscale Funnel serve a custom domain?) with a definitive answer backed by official docs and a specific GitHub issue reference
  • Four sub-questions are well-structured and build on each other logically
  • Cost/complexity comparison table is thorough and covers the right dimensions
  • Phased recommendation is sound -- the rationale for deferring Cloudflare Tunnel until after Keycloak stabilizes avoids changing redirect URIs twice
  • Follow-up tickets are concrete with per-ticket effort estimates
  • Sources section cites six external references (Tailscale docs, GitHub issue, Cloudflare docs, pricing page, GoDaddy docs, community blog post)
  • Mermaid diagrams for both phases are clear and consistent with existing diagram conventions in the repo

BLOCKERS

None. This is a documentation-only spike with no code changes. No BLOCKER criteria are triggered:

  • No new functionality requiring test coverage (documentation only)
  • No user input handling
  • No secrets or credentials in the diff
  • No auth/security code paths

NITS

  1. Stale branch base: The PR branch appears to be based on a commit before PR #27 (Feature Flags guide) was merged. The README diff context around the insertion point does not include the - [Feature Flags](docs/feature-flags.md) line that now exists on main (line 16). Forgejo reports mergeable: true, so git should auto-merge cleanly, but verify after merge that the docs list ordering is correct (Custom Domain should appear after Pipeline and before Visibility, with Feature Flags also present).

  2. README ordering convention: The new entry is inserted between Pipeline and Visibility. This is alphabetically inconsistent with the existing list (Architecture, Infrastructure, Pipeline, then jumping to Custom Domain). Consider whether the docs list should be alphabetical or grouped by category. Currently it appears to be roughly grouped by topic area, in which case Custom Domain fits logically after Pipeline/Infrastructure. Not blocking.

  3. Minor: "Custom Domain (Planned)" in infrastructure.md: The existing docs/infrastructure.md (line 167) already has a "Custom Domain (Planned)" section that references Spike #15. After this spike merges, that section could be updated to link to the new docs/custom-domain.md instead of pointing at the issue. This is out of scope for this PR but worth noting as follow-up.

  4. Source link verifiability: The community blog post source (https://www.alif.web.id/posts/k3s-cluster-with-cloudflared-tailscale) may be ephemeral. The other five sources are official docs/GitHub -- solid. Consider noting that the community source is supplementary, not authoritative. Not blocking.

SOP COMPLIANCE

  • Branch named after issue: 15-custom-domain-spike follows {issue-number}-{kebab-case-purpose} convention
  • PR body follows template: Summary, Changes, Test Plan, Review Checklist, Related Notes all present
  • Related Notes references parent issue: ldraney/palinks #15 referenced, Closes #15 present
  • No secrets committed: diff contains only Markdown documentation
  • No unnecessary file changes: 2 files changed (README.md docs list entry + new spike document) -- both directly related to the spike
  • Commit messages: not visible in diff, but PR title is descriptive
  • Closes #15 present in PR body

PROCESS OBSERVATIONS

  • This spike follows a healthy pattern: research before implementation. The phased recommendation avoids coupling domain migration with the Keycloak auth migration (issue #16), which reduces change failure risk.
  • The follow-up tickets section is well-structured with effort estimates, making it easy to plan subsequent work.
  • Documentation-only spikes carry zero deployment risk. The only risk is merging stale information, which this review has validated against the current codebase.

VERDICT: APPROVED

## PR #25 Review ### DOMAIN REVIEW **Tech stack:** Documentation-only spike (Markdown). No application code changes. The domain expertise applied here is infrastructure/networking (Tailscale, Cloudflare Tunnel, k8s ingress, DNS, TLS) since the spike evaluates infrastructure options. **Content accuracy verified against codebase:** - The spike correctly identifies that Tailscale Funnel is the current ingress path (confirmed in `docs/infrastructure.md` and `docs/architecture.md`) - The k8s Service/Pod/Puma architecture described in the mermaid diagrams matches the existing diagrams in `docs/infrastructure.md` lines 56-78 - The mention of Keycloak integration (issue #16) and visibility tiers (issue #17) as dependencies is consistent -- both issues exist and are open - Cross-repo change matrix (pal-e-platform, pal-e-services, pal-e-deployments, palinks) aligns with the actual infrastructure layout documented in `docs/infrastructure.md` - The claim that `config.hosts` needs updating is correct per Rails security defaults - References to CNPG, Harbor, ArgoCD, and SOPS-encrypted secrets match the documented stack **Spike quality assessment:** - Research question is clearly framed (can Tailscale Funnel serve a custom domain?) with a definitive answer backed by official docs and a specific GitHub issue reference - Four sub-questions are well-structured and build on each other logically - Cost/complexity comparison table is thorough and covers the right dimensions - Phased recommendation is sound -- the rationale for deferring Cloudflare Tunnel until after Keycloak stabilizes avoids changing redirect URIs twice - Follow-up tickets are concrete with per-ticket effort estimates - Sources section cites six external references (Tailscale docs, GitHub issue, Cloudflare docs, pricing page, GoDaddy docs, community blog post) - Mermaid diagrams for both phases are clear and consistent with existing diagram conventions in the repo ### BLOCKERS None. This is a documentation-only spike with no code changes. No BLOCKER criteria are triggered: - No new functionality requiring test coverage (documentation only) - No user input handling - No secrets or credentials in the diff - No auth/security code paths ### NITS 1. **Stale branch base:** The PR branch appears to be based on a commit before PR #27 (Feature Flags guide) was merged. The README diff context around the insertion point does not include the `- [Feature Flags](docs/feature-flags.md)` line that now exists on main (line 16). Forgejo reports `mergeable: true`, so git should auto-merge cleanly, but verify after merge that the docs list ordering is correct (Custom Domain should appear after Pipeline and before Visibility, with Feature Flags also present). 2. **README ordering convention:** The new entry is inserted between Pipeline and Visibility. This is alphabetically inconsistent with the existing list (Architecture, Infrastructure, Pipeline, then jumping to Custom Domain). Consider whether the docs list should be alphabetical or grouped by category. Currently it appears to be roughly grouped by topic area, in which case Custom Domain fits logically after Pipeline/Infrastructure. Not blocking. 3. **Minor: "Custom Domain (Planned)" in infrastructure.md:** The existing `docs/infrastructure.md` (line 167) already has a "Custom Domain (Planned)" section that references Spike #15. After this spike merges, that section could be updated to link to the new `docs/custom-domain.md` instead of pointing at the issue. This is out of scope for this PR but worth noting as follow-up. 4. **Source link verifiability:** The community blog post source (`https://www.alif.web.id/posts/k3s-cluster-with-cloudflared-tailscale`) may be ephemeral. The other five sources are official docs/GitHub -- solid. Consider noting that the community source is supplementary, not authoritative. Not blocking. ### SOP COMPLIANCE - [x] Branch named after issue: `15-custom-domain-spike` follows `{issue-number}-{kebab-case-purpose}` convention - [x] PR body follows template: Summary, Changes, Test Plan, Review Checklist, Related Notes all present - [x] Related Notes references parent issue: `ldraney/palinks #15` referenced, `Closes #15` present - [x] No secrets committed: diff contains only Markdown documentation - [x] No unnecessary file changes: 2 files changed (README.md docs list entry + new spike document) -- both directly related to the spike - [x] Commit messages: not visible in diff, but PR title is descriptive - [x] `Closes #15` present in PR body ### PROCESS OBSERVATIONS - This spike follows a healthy pattern: research before implementation. The phased recommendation avoids coupling domain migration with the Keycloak auth migration (issue #16), which reduces change failure risk. - The follow-up tickets section is well-structured with effort estimates, making it easy to plan subsequent work. - Documentation-only spikes carry zero deployment risk. The only risk is merging stale information, which this review has validated against the current codebase. ### VERDICT: APPROVED
ldraney deleted branch 15-custom-domain-spike 2026-06-08 03:16:41 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ldraney/palinks!25
No description provided.