
여러 프로젝트를 진행하다보니 서버 비용이 부담되어 개인 서버에 프로젝트를 올려 임시로 사용하다보니 점점 엉망이 되어가는 도커 내용을 보게 되면서 관리의 필요성이 느껴졌다!
최근 dokploy라는 개인 서버 배포용 프로그램이 새로 생기면서 개인 서버에서 배포가 용이해진 부분을 보게 되었고 한 번 적용해보기로 했다!
기존에는 서비스로서의 플랫폼(Platform-as-a-service, PaaS)으로 애플리케이션 소프트웨어 플랫폼이 제3사를 통해 제공되는 heroku와 같은 서비스를 보여 간단한 배포의 장점이 있는 서비스였지만 자유롭게 사용하기에는 비용 문제가 있어서 그렇지 못했다… 이번에 dokploy 서비스를 보며 집에서 썩어가던 NAS에 생기를 불어 넣기로 했다.
기존의 내 서버는 OMV를 통해 여러 서버 프로그램을 구동 시키다가 최근에 중요한 부분만 제외하고 다 옮기면서 기존 OMV 서버의 도커를 정리하고 있다 원래는 너무 많다보니… 서비스를 한 번에 관리하기 어려웠다.

OMV 서버를 사용 중이고, ssh는 keychain을 통해 연결하고 있으므로 수동 설치하기로 했다
https://docs.dokploy.com/docs/core/manual-installation
설치를 완료하고 3000번 포트로 접속하면 dokploy가 반겨준다!

간단하게 기능은 다음과 같이 구성 되어있다.

기존에는 nginx로 구성된 NPM을 사용 중이었으나 프로젝트 관리 측면에서 traefik이 더 편리할 것 같아 traefik으로 이전하였다
기존에 구성하던 NGINX는 다음과 같은 화면을 구성 되아있어서 간단한 구성을 가지고 있다.
그러나, 프로젝트와의 연관성이 떨어져 매번 따로 프로젝트를 생성한 후, 프록시를 걸어줘야했음으로 한 번에 관리하기 어려웠다.

하지만, dokploy는 프로젝트 구성과 함께 프로젝트마다 traefik을 통해 관리하기 편하였고 옮겨오게 되었다.


그후, 객체기반 데이터 관리를 사용하는 경우가 많았어서 MinIO를
사용하기로 하였다.
MinIO는 OMV에서 제공하는 Podman 기반의 이미지를 사용하였고 업데이트 관리와 함께 관리할 수 있어서 설치하게 되었다.
간단한 GUI 기반이라 설정하기 편하였다.

그후, dokploy의 S3기능을 통하여 연결을 확인하고자 한다.

OMV에서 설정했던 아이디 비번을 사용하여 로그인한다.

처음에는 다음과 같이 버킷이 존재하지 않는다.

만들고 싶은 버킷을 만든 후, Access Policy를 Public으로 설정해준다.


좌측 베너에서 액세스키를 생성하자! 해당 액세스 키를 통해 데이터에 접근하여 수정이 가능하다
(해당 코드는 예시로 남겨져 있고 실제 사용하는 액세스 키는 아니다)


다시 Dokploy로 돌아와서 설정을 이어가자

하지만 테스트를 실행하면 다음과 같은 오류가 발생한다…
해당 오류는 인증 관련된 부분에 이상이 있어 발생하는 것이라고 한다…
분명 틀린게 없는데 계속 발생하는 오류를 추적하고자 traefik의 로그를 이용하여 내용을 살펴 봤다.

다음은 순서대로 treafik에서 가져온 요청 및 응답이다
{
"ClientAddr":"192.168.45.1:42016",
"ClientHost":"192.168.45.1",
"ClientPort":"42016",
"ClientUsername":"-",
"DownstreamContentSize":27425,
"DownstreamStatus":200,
"Duration":10296794,
"OriginContentSize":27425,
"OriginDuration":10186780,
"OriginStatus":200,
"Overhead":110014,
"RequestAddr":"---",
"RequestContentSize":0,
"RequestCount":62,
"RequestHost":"---",
"RequestMethod":"GET",
"RequestPath":"/tairot?delimiter=\u0026encoding-type=url\u0026list-type=2\u0026max-keys=1000\u0026prefix=",
"RequestPort":"-",
"RequestProtocol":"HTTP/2.0",
"RequestScheme":"https",
"RetryAttempts":0,
"RouterName":"main-s3storage-z9kjm5-router-websecure-2-1@file",
"ServiceAddr":"192.168.45.10:9000",
"ServiceName":"main-s3storage-z9kjm5-service-2-1@file",
"ServiceURL":"http://192.168.45.10:9000",
"StartLocal":"2025-06-10T10:11:24.300029533Z",
"StartUTC":"2025-06-10T10:11:24.300029533Z",
"TLSCipher":"TLS_AES_128_GCM_SHA256",
"TLSVersion":"1.3",
"downstream_Accept-Ranges":"bytes",
"downstream_Alt-Svc":"h3=\":443\"; ma=2592000",
"downstream_Content-Length":"27425",
"downstream_Content-Type":"application/xml",
"downstream_Date":"Tue, 10 Jun 2025 10:11:24 GMT",
"downstream_Server":"MinIO",
"downstream_Strict-Transport-Security":"max-age=31536000; includeSubDomains",
"downstream_Vary":"Origin,Accept-Encoding",
"downstream_X-Amz-Id-2":"dd9025bab4ad464b049177c95eb6ebf374d3b3fd1af9251148b658df7ac2e3e8",
"downstream_X-Amz-Request-Id":"1847A6B6CC6C7F18",
"downstream_X-Content-Type-Options":"nosniff",
"downstream_X-Ratelimit-Limit":"4186",
"downstream_X-Ratelimit-Remaining":"4186",
"downstream_X-Xss-Protection":"1; mode=block",
"entryPointName":"websecure",
"level":"info",
"msg":"",
"origin_Accept-Ranges":"bytes",
"origin_Alt-Svc":"h3=\":443\"; ma=2592000",
"origin_Content-Length":"27425",
"origin_Content-Type":"application/xml",
"origin_Date":"Tue, 10 Jun 2025 10:11:24 GMT",
"origin_Server":"MinIO",
"origin_Strict-Transport-Security":"max-age=31536000; includeSubDomains",
"origin_Vary":"Origin,Accept-Encoding",
"origin_X-Amz-Id-2":"dd9025bab4ad464b049177c95eb6ebf374d3b3fd1af9251148b658df7ac2e3e8",
"origin_X-Amz-Request-Id":"1847A6B6CC6C7F18",
"origin_X-Content-Type-Options":"nosniff",
"origin_X-Ratelimit-Limit":"4186",
"origin_X-Ratelimit-Remaining":"4186",
"origin_X-Xss-Protection":"1; mode=block",
"request_Accept-Encoding":"identity",
"request_Amz-Sdk-Invocation-Id":"1ba208fa-6335-495f-87e5-dcb0a9512be6",
"request_Amz-Sdk-Request":"attempt=1; max=10",
"request_Authorization":"AWS4-HMAC-SHA256 Credential=LgGqTwJtz10E2EpSwUYG/20250610/us-east-1/s3/aws4_request, SignedHeaders=accept-encoding;amz-sdk-invocation-id;amz-sdk-request;host;x-amz-content-sha256;x-amz-date, Signature=1e5bd5f917ac705ef3c14ca5550c12fc71f220dc900912189d67adf64826381b",
"request_User-Agent":"REDACTED",
"request_X-Amz-Content-Sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"request_X-Amz-Date":"20250610T101124Z",
"request_X-Forwarded-Host":"",
"request_X-Forwarded-Port":"443",
"request_X-Forwarded-Proto":"https",
"request_X-Forwarded-Server":"5fc44aceaf4b",
"request_X-Real-Ip":"192.168.45.1",
"time":"2025-06-10T10:11:24Z"
}
{
"ClientAddr":"172.70.223.125:31814",
"ClientHost":"172.70.223.125",
"ClientPort":"31814",
"ClientUsername":"-",
"DownstreamContentSize":399,
"DownstreamStatus":403,
"Duration":1868301,
"OriginContentSize":399,
"OriginDuration":1565301,
"OriginStatus":403,
"Overhead":303000,
"RequestAddr":"----",
"RequestContentSize":0,
"RequestCount":100,
"RequestHost":"---",
"RequestMethod":"GET",
"RequestPath":"/tairot?delimiter=\u0026encoding-type=url\u0026list-type=2\u0026max-keys=1000\u0026prefix=",
"RequestPort":"-",
"RequestProtocol":"HTTP/2.0",
"RequestScheme":"https",
"RetryAttempts":0,
"RouterName":"main-s3storage-z9kjm5-router-websecure-2-1@file",
"ServiceAddr":"192.168.45.10:9000",
"ServiceName":"main-s3storage-z9kjm5-service-2-1@file",
"ServiceURL":"http://192.168.45.10:9000",
"StartLocal":"2025-06-10T10:21:44.183776809Z",
"StartUTC":"2025-06-10T10:21:44.183776809Z",
"TLSCipher":"TLS_AES_128_GCM_SHA256",
"TLSVersion":"1.3",
"downstream_Accept-Ranges":"bytes",
"downstream_Alt-Svc":"h3=\":443\"; ma=2592000",
"downstream_Content-Length":"399",
"downstream_Content-Type":"application/xml",
"downstream_Date":"Tue, 10 Jun 2025 10:21:44 GMT",
"downstream_Server":"MinIO",
"downstream_Strict-Transport-Security":"max-age=31536000; includeSubDomains",
"downstream_Vary":"Origin,Accept-Encoding",
"downstream_X-Amz-Id-2":"dd9025bab4ad464b049177c95eb6ebf374d3b3fd1af9251148b658df7ac2e3e8",
"downstream_X-Amz-Request-Id":"1847A7472065F074",
"downstream_X-Content-Type-Options":"nosniff",
"downstream_X-Ratelimit-Limit":"4186",
"downstream_X-Ratelimit-Remaining":"4186",
"downstream_X-Xss-Protection":"1; mode=block",
"entryPointName":"websecure",
"level":"info",
"msg":"",
"origin_Accept-Ranges":"bytes",
"origin_Alt-Svc":"h3=\":443\"; ma=2592000",
"origin_Content-Length":"399",
"origin_Content-Type":"application/xml",
"origin_Date":"Tue, 10 Jun 2025 10:21:44 GMT",
"origin_Server":"MinIO",
"origin_Strict-Transport-Security":"max-age=31536000; includeSubDomains",
"origin_Vary":"Origin,Accept-Encoding",
"origin_X-Amz-Id-2":"dd9025bab4ad464b049177c95eb6ebf374d3b3fd1af9251148b658df7ac2e3e8",
"origin_X-Amz-Request-Id":"1847A7472065F074",
"origin_X-Content-Type-Options":"nosniff",
"origin_X-Ratelimit-Limit":"4186",
"origin_X-Ratelimit-Remaining":"4186",
"origin_X-Xss-Protection":"1; mode=block",
"request_Accept-Encoding":"gzip, br",
"request_Amz-Sdk-Invocation-Id":"76afa91a-5fe9-43bc-9146-d6176b005bb7",
"request_Amz-Sdk-Request":"attempt=1; max=10",
"request_Authorization":"AWS4-HMAC-SHA256 Credential=LgGqTwJtz10E2EpSwUYG/20250610/us-east-1/s3/aws4_request, SignedHeaders=accept-encoding;amz-sdk-invocation-id;amz-sdk-request;host;x-amz-content-sha256;x-amz-date, Signature=3a20e3dd8881576f08b6da3ead9d865c6b13a698faf3e511238a337c797a1bc6",
"request_Cdn-Loop":"cloudflare; loops=1",
"request_Cf-Connecting-Ip":"211.213.118.196",
"request_Cf-Ipcountry":"KR",
"request_Cf-Ray":"94d824be388ee009-NRT",
"request_Cf-Visitor":"{\"scheme\":\"https\"}",
"request_User-Agent":"REDACTED",
"request_X-Amz-Content-Sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"request_X-Amz-Date":"20250610T102143Z",
"request_X-Forwarded-Host":"",
"request_X-Forwarded-Port":"443",
"request_X-Forwarded-Proto":"https",
"request_X-Forwarded-Server":"5fc44aceaf4b",
"request_X-Real-Ip":"172.70.223.125",
"time":"2025-06-10T10:21:44Z"
}
다음과 같이 인증 부분 요청에 대한 부분에 오류는 발견되지 않았으나 인증에 문제가 생기고 있었고 구글링을 통해 오류를 찾기로 하였다.
"request_Authorization":"AWS4-HMAC-SHA256 Credential=LgGqTwJtz10E2EpSwUYG/20250610/us-east-1/s3/aws4_request, SignedHeaders=accept-encoding;amz-sdk-invocation-id;amz-sdk-request;host;x-amz-content-sha256;x-amz-date, Signature=1e5bd5f917ac705ef3c14ca5550c12fc71f220dc900912189d67adf64826381b",
"request_Authorization":"AWS4-HMAC-SHA256 Credential=LgGqTwJtz10E2EpSwUYG/20250610/us-east-1/s3/aws4_request, SignedHeaders=accept-encoding;amz-sdk-invocation-id;amz-sdk-request;host;x-amz-content-sha256;x-amz-date, Signature=3a20e3dd8881576f08b6da3ead9d865c6b13a698faf3e511238a337c797a1bc6",
나와 같이 오류에 대해 호소하고 있는 사람을 찾았다.
https://community.cloudflare.com/t/how-cloudflare-handle-head-request/238093

현재 CloudFlare을 통하여 내 서버의 주소를 리버스 프로싱하여 사용하고 있다 그러나, 공식 사이트의 내용을 확인해본 결과 인코딩을 통해 넘기는 문제가 있었고 이 문제에 관하여 여러 해결방법을 적용하였다.
Cloudflare will take into consideration the
accept-encodingheader value in website visitors' requests when sending responses to those visitors. However, when requesting content from your origin server, Cloudflare will send a differentAccept-Encodingheader, supporting Brotli and Gzip compression.
규칙을 통해 압축사용을 하지않게 만들었지만 기본적으로 cloudflare DNS 리버시 프로싱 기능이 헤더의 일부 내용을 변경한다는 사실을 알았다.
아래와 같이 여러 캐시 적용 방지와 압축 기능 적용 방지를 적용하였지만 오류는 해결되지 않았다.


다음과 같이 요청의 형식이 계속 변화하는 것을 확인하였고 결론적으로 리버스 프로싱을 해제하여 연결을 성공하였다
"request_Accept-Encoding":"identity",
"request_Accept-Encoding":"gzip, br",