Architecture: Rails App (Shared Pattern)
Architecture: Rails App (Shared Pattern)
Common deployment architecture for Rails applications on the pal-e platform. Currently implemented by
landscaping-assistant (production) and palinks (in development). New Rails apps SHOULD follow this pattern unless there is a documented reason to diverge.Diagram
Components
| Component | Purpose | Notes |
|---|---|---|
| Rails Pod | Application server | Rails 8.1, Hotwire (Turbo + Stimulus). Monolithic -- no separate frontend, Rails serves HTML directly. Importmap for JS, no build pipeline. |
| CNPG PostgreSQL 17 | Primary database | CloudNativePG operator on k3s. Shared cluster, per-app databases. Handles failover, backups, WAL archiving. |
| Keycloak OIDC | Authentication (opt-in) | Authorization Code + PKCE via OmniAuth. OAuth 2.1 compliant. NOT all apps have this -- landscaping-assistant: yes, palinks: planned. |
| MinIO S3 | Object storage (opt-in) | ActiveStorage with S3-compatible backend. Local disk in dev. Only for apps with file/image uploads -- landscaping-assistant: yes, palinks: no. |
| Woodpecker CI | CI pipeline | Triggered by Forgejo webhook. Stages: test (rspec + postgres service), build (multi-stage Docker), push to Harbor. |
| Harbor Registry | Container image store | harbor.tail5b443a.ts.net. Images tagged with full commit SHA. Trivy vulnerability scanning. |
| ArgoCD + Image Updater | Continuous deployment | Image Updater detects new SHA tags, writes to kustomize overlay in pal-e-deployments. ArgoCD syncs to cluster. |
| ServiceMonitor | Metrics scraping | Prometheus scrapes /metrics endpoint exposed by yabeda-rails + yabeda-prometheus. |
| Grafana + Loki | Observability | Grafana dashboards for metrics. Loki + Promtail for log aggregation. Structured JSON logs via lograge. |
| Caddy (Hetzner edge) | Public reverse proxy + TLS | 178.156.129.142, CPX11 Ashburn. Auto-TLS for custom domains. Connected to home lab via Tailscale mesh (tail5b443a.ts.net). |
| GoDaddy DNS | Domain management | A records pointing custom domains to Hetzner edge IP. Managed by godaddy-tofu provider via pal-e-platform/terraform/. |
| Kustomize overlays | Deployment manifests | Live in pal-e-deployments repo. Follow convention-kustomize-overlay pattern. Image tags updated by ArgoCD Image Updater. |
Deployment Pipeline
- Push to Forgejo -- triggers Woodpecker CI webhook
- CI: Test --
bundle exec rspecagainst PostgreSQL service container (version 17, matching prod) - CI: Build -- multi-stage Docker build
- CI: Push -- push image to Harbor (
harbor.tail5b443a.ts.net/project/app-name:<commit-sha>) - ArgoCD Image Updater -- detects new tag (regexp
^[0-9a-f]{7,40}$), writes SHA to kustomize overlay - ArgoCD Sync -- applies updated kustomization to k3s cluster
- Auto-migration --
rails db:migrateruns on deploy
Public Domain Routing
Three steps to a public domain: (1) godaddy-tofu provider creates A record pointing to 178.156.129.142, (2) Caddy on Hetzner edge terminates TLS and reverse-proxies via Tailscale, (3) k3s ClusterIP service routes to Rails pod.
Variations by App
| Concern | landscaping-assistant | palinks |
|---|---|---|
| Auth | Keycloak (Auth Code + PKCE, OmniAuth). Five-role model. Session-based. | Planned, not yet integrated. |
| Object storage | ActiveStorage + MinIO for photo uploads. | Not needed (link app, no file uploads). |
| Mobile | Turbo Native iOS build. Dev funnel at landscaping-dev.tail5b443a.ts.net. | Web-only. |
| Observability depth | Full Grafana ecosystem: 26 dashboards, golden signals, blackbox probes, DORA metrics. | ServiceMonitor deployed, metrics parity achieved. |
| Domain-specific services | Nominatim geocoding. | None. |
| Test suite | 230+ RSpec specs (model, request, system). Capybara + Cuprite. | In development. |
| Custom domain | landscaping-assistant.app (when configured). | palinks.app (DNS pending godaddy-tofu integration). |
Key Decisions
- Monolithic Rails over microservices -- single deployable unit per app. No API gateway complexity. Frontend and backend in one repo, one process.
- Hotwire over SPA -- server-rendered HTML with progressive enhancement via Turbo Frames/Streams. No JS build pipeline (importmap). Turbo Native wraps the same app for mobile.
- CNPG shared cluster -- one CloudNativePG operator instance, separate databases per app. Operator manages failover, backups, WAL archiving. CI postgres version pinned to match prod (17).
- Keycloak opt-in, not mandatory -- apps that need auth use Authorization Code + PKCE (OAuth 2.1 compliant). ROPC/Direct Access Grants rejected as deprecated (RFC 9700). Apps without auth needs skip Keycloak entirely.
- MinIO opt-in -- only apps with file/image storage use ActiveStorage + MinIO. S3-compatible API provides portability. Dev uses local disk.
- Hetzner edge for public ingress -- Caddy handles TLS termination on a public IP. Tailscale mesh avoids exposing the home lab directly. Single edge node at 178.156.129.142 serves all custom domains.
- Commit-SHA image tags -- immutable, traceable. ArgoCD Image Updater automates tag detection and kustomize overlay updates.
- RSpec + Capybara + Cuprite for testing -- system specs run headless Chromium via CDP. No Selenium dependency.
- Structured logging via lograge -- JSON output collected by Promtail into Loki. No unstructured Rails default logs in production.
Related
project-palinks-- palinks project pageproject-landscaping-assistant-- landscaping-assistant project pagearch-godaddy-tofu-- DNS provider architecture (manages A records for custom domains)convention-kustomize-overlay-- deployment overlay pattern used by pal-e-deploymentsconvention-pipeline-stages-- CI pipeline stage conventionconvention-frontend-css-- CSS conventions for frontend work