Docker Compose를 사용해 앱의 Nginx 앞에 Caddy 배치하기

IONOS Cloud Panel의 방화벽 설정에서 UDP 443 포트를 열기 위해(영국 거주자입니다) Discourse 포럼 서버 중 하나를 업그레이드했습니다.

해당 포럼의 사용자 수는 많지 않지만 카테고리가 많아 CDN을 사용하지 않고 있습니다.

nginx를 통해 IP 지오로케이션이 올바르게 작동하도록 하는 데 일부 어려움이 있었지만, 현재 포럼은 /logs에 문제가 없이 잘 작동하고 있습니다.

Docker Compose로 로테이팅 프록시 로그를 설정해 두었고, Caddy가 프록시 로그의 민감한 필드를 자동으로 마스킹하는 기능이 있다는 점을 높이 평가합니다. Nginx에서는 이런 기능이 있다는 것을 이전에는 잘 인지하지 못했기 때문입니다. 물론, 프로덕션 환경에서 Discourse를 사용한 2년 동안 프록시 로그를 사용한 경험은 두 번밖에 없긴 합니다.

초보자용 Docker 설치 가이드에서 현재처럼 모바일 친화적인 포럼을 구축하기 위해 제가 실행한 명령어들과 그 결과를 정리한 가이드를 공개한다면 얼마나 많은 관심을 받을지 궁금합니다. 향후 유지보수를 위한 명령어(예: Caddy의 최신 릴리스로 업그레이드하기)도 포함할 수 있지만, 아직 IONOS L VPS에서 해당 명령어들을 테스트해 보지는 않았습니다.

참고로, 이 PR이 있습니다: Add Caddy web server template as nginx alternative - Pull Request #952 - discourse/discourse_docker - GitHub

감사합니다. 그 PR을 아직 보지 못했습니다. 흥미롭네요. 제 환경은 Discourse의 nginx 웹 서버를 그대로 두고 Caddy를 Docker Compose에서 별도로 실행하여 그 앞에 배치한 점이 약간 다릅니다. 주로 엣지에서 HTTP/3를 제공하기 위해서입니다.

Discourse 측에서 작은 변경 사항이 하나 있습니다. 유닉스 소켓을 통해 요청이 들어올 때 nginx가 실제 클라이언트 IP로 Caddy의 X-Forwarded-For 값을 사용하도록 영구적인 app.yml 훅을 추가했습니다. 처음에는 Caddy의 X-Real-IP 우회 설정이 필요했지만, nginx 측 수정 사항을 추가한 후 이를 제거하고도 Discourse가 여전히 올바른 클라이언트 IP를 기록하는지 확인했습니다.

또한 Caddy 설정에서 HTTP/3은 활성화한 채로 QUIC 0-RTT/early data를 명시적으로 비활성화하여, 추가적인 early-data 엣지 케이스를 피했습니다.

제 방식은 사이트 레벨에서 Caddy의 HTTP 액세스 로깅도 활성화하여, Caddy의 기본 민감한 자격 증명 헤더 마스킹(redaction) 기능을 제공합니다. 생성된 Docker json-file 로그는 25 MB × 3 기준으로 로테이션합니다. 현재 해당 PR에는 로테이션 로그 블록이 Caddy의 전역 옵션에 있는 것을 확인했는데, Caddy 문서에서는 이를 런타임 로깅 설정으로 설명하고 HTTP 액세스 로깅 설정으로 설명하지 않습니다.

따라서 완전히 손대지 않은 표준 설치와는 약간 다르지만, nginx를 Caddy로 완전히 교체하는 방식과는 다른 접근법입니다.

해당 PR을 더 자세히 살펴보겠습니다. 당장은 바로 가이드를 작성하기보다는, 이 접근법에 대한 충분한 관심이 있는지, 단계별 가이드를 만드는 것이 가치가 있는지 확인하는 데 주로 관심이 있습니다.

위에서 말씀드린 것에 대한 정정/보충 설명입니다.

처음에 결과를 다음과 같이 설명했을 때:

모바일 친화적인 포럼이라고 한 것은, iOS Safari PWA를 열 때 초기 왕복 처리가 이전보다 너무 느리게 느껴졌던 부분도 제가 눈치챈 것 중 하나였기 때문입니다.


하지만 나중에 쓴 답변에 다음 내용을 포함시킨 것은 지금은 후회됩니다:

0-RTT를 비활성화한 것은 클라이언트 IP 문제를 진단하는 과정에서 시도했던 것일 뿐, 사실 필요하지 않았던 것으로 보입니다. unix: 관련 PostgreSQL 오류 두 건은 0rtt off가 이미 설정된 상태에서도 계속 발생했습니다.

해당 오류를 해결한 것으로 보이는 변경 사항은, 유닉스 소켓으로 요청이 도착했을 때 실제 클라이언트 IP로 Caddy의 X-Forwarded-For 값을 사용하도록 nginx를 수정하는 영구적인 app.yml 훅이었습니다.

따라서 저는 이제 0rtt off 설정을 제거하고 Caddy의 기본 동작을 복원했습니다. HTTP/3은 여전히 정상 작동하며, 올바른 클라이언트 IP가 Discourse로 계속 전달되고 있습니다.

결국 제 단계별 가이드를 작성할 만큼 충분한 관심이 생긴다면, QUIC 0-RTT 비활성화를 설정의 필수 부분으로 포함하지 않을 것입니다.

바닐라 Nginx에서도 가능합니다: https://quic.nginx.org/

감사합니다. 현재 제 Discourse app 컨테이너에 포함된 nginx를 확인해 보니, 말씀하신 대로 HTTP/3을 직접 제공할 수 있습니다:

nginx 1.26.3-3+deb13u7
OpenSSL 3.5.6
--with-http_v3_module

따라서 Discourse의 기존 nginx를 통해 직접 HTTP/3을 사용하는 것은 확실히 진정한 대안입니다.

다만 이 특정 설정에서 Caddy를 선호할 수 있는 이유는 0-RTT 기능 때문입니다. 앞서 제가 수정한 내용 이후로 Caddy의 기본 QUIC 0-RTT 동작을 다시 활성화했는데, iOS Safari PWA에서 제가 개선하려던 로딩 경험에 상당한 향상이 있음을 확인했습니다.

nginx 문서에서 제가 파악한 바로는, OpenSSL로 빌드된 경우 ssl_early_data 설정과 관계없이 nginx 1.29.1 이전 버전에서는 0-RTT를 활성화할 수 없습니다. 따라서 현재 제 Discourse 컨테이너에 있는 nginx 1.26.3은 HTTP/3은 지원하지만 해당 특정 최적화는 적용할 수 없습니다.

그래서 저는 Caddy가 HTTP/3를 위해 반드시 필요하다고 단정 짓기보다는 두 가지 접근 방식을 비교해 보고 싶습니다.