Caddyfile 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. # Production-ready Caddy config fronting both api and ui.
  2. #
  3. # Two-hostname pattern (recommended):
  4. # reputation.example.com → ui :8080
  5. # reputation-api.example.com → api :8081
  6. #
  7. # Caddy auto-provisions Let's Encrypt certs for both names. ACME HTTP-01
  8. # requires port 80 to be reachable from the internet; ACME TLS-ALPN
  9. # (default in Caddy 2) uses port 443.
  10. #
  11. # Replace the two hostnames with your own and put this file in
  12. # /etc/caddy/Caddyfile (or `caddy run --config Caddyfile` for a manual
  13. # deployment).
  14. #
  15. # If the api and ui run on the same host as Caddy, the upstream
  16. # addresses below ("ui:8080" / "api:8081") work when Caddy joins the
  17. # Docker network. If Caddy runs on the host, use 127.0.0.1:8080 /
  18. # 127.0.0.1:8081 instead.
  19. # ----- UI (humans in browsers) -----------------------------------
  20. reputation.example.com {
  21. encode zstd gzip
  22. # Security headers — sensible defaults for an admin UI.
  23. header {
  24. Strict-Transport-Security "max-age=31536000; includeSubDomains"
  25. X-Frame-Options "DENY"
  26. X-Content-Type-Options "nosniff"
  27. Referrer-Policy "strict-origin-when-cross-origin"
  28. # Tailwind + Alpine + htmx + chart.js — relax `unsafe-inline`
  29. # if you tighten Tailwind to no inline styles. The current UI
  30. # ships a small inline `<head>` script for theme-before-paint
  31. # which needs `unsafe-inline` on script-src too.
  32. Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'"
  33. }
  34. reverse_proxy ui:8080 {
  35. # Forward client info so the ui can log + audit accurately.
  36. header_up X-Forwarded-Proto {scheme}
  37. header_up X-Forwarded-Host {host}
  38. header_up X-Real-IP {remote}
  39. }
  40. }
  41. # ----- API (machine clients + ui server-to-server) ---------------
  42. reputation-api.example.com {
  43. encode zstd gzip
  44. header {
  45. Strict-Transport-Security "max-age=31536000; includeSubDomains"
  46. X-Content-Type-Options "nosniff"
  47. # The api never frames anything; allowlist nothing.
  48. X-Frame-Options "DENY"
  49. }
  50. # /internal/* must NEVER traverse the public proxy. The api's own
  51. # Caddyfile inside the container blocks RFC1918-only paths, but
  52. # we belt-and-braces 404 it here so a misconfigured upstream
  53. # doesn't accidentally expose them.
  54. @internal {
  55. path /internal/*
  56. }
  57. handle @internal {
  58. respond 404
  59. }
  60. reverse_proxy api:8081 {
  61. header_up X-Forwarded-Proto {scheme}
  62. header_up X-Forwarded-Host {host}
  63. header_up X-Real-IP {remote}
  64. }
  65. }
  66. # ---- Single-hostname alternative --------------------------------
  67. # If you don't want two hostnames, point everything at one and route
  68. # by path prefix. Drop the two blocks above and use this one instead:
  69. #
  70. # reputation.example.com {
  71. # encode zstd gzip
  72. # @api path /api/* /healthz
  73. # handle @api {
  74. # reverse_proxy api:8081
  75. # }
  76. # handle {
  77. # reverse_proxy ui:8080
  78. # }
  79. # }
  80. #
  81. # Caveat: future browser-direct frontends would then share an origin
  82. # with the api, which simplifies CORS but couples deployment.