- Published on
GitLab CI DinD TLS 실패, 원인별 해결법
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
GitLab CI에서 Docker-in-Docker(DinD)를 쓰다 보면 어느 날 갑자기 docker login, docker build, docker push가 TLS 관련 에러로 무너집니다. 특히 러너가 업데이트되거나 docker:dind 이미지 태그가 바뀌는 순간, 이전에는 우연히 통과하던 설정이 더 이상 동작하지 않는 일이 흔합니다.
이 글은 “DinD에서 TLS가 왜 깨지는지”를 증상별로 분류하고, 가장 많이 쓰이는 해결 루트를 재현 가능한 형태의 .gitlab-ci.yml 예제로 제공합니다. 동일한 증상이라도 원인이 다를 수 있으니, 로그 메시지 패턴을 기준으로 빠르게 분기해보세요.
참고로 CI에서 인증/네트워크 이슈를 점검하는 접근은 API 인증 오류를 추적할 때와 유사합니다. 체크리스트 방식이 필요하다면 OpenAI Responses API 401 403 인증오류 점검 가이드도 함께 보면 도움이 됩니다.
DinD의 TLS 구조를 먼저 이해하기
GitLab CI에서 DinD를 쓸 때 일반적인 구성은 아래와 같습니다.
- Job 컨테이너:
docker:24같은 Docker CLI 이미지 - Service 컨테이너:
docker:24-dind(Docker daemon) - Job 컨테이너의 Docker CLI가 Service 컨테이너의 Docker daemon에 TCP로 접속
여기서 TLS는 주로 docker:dind가 제공하는 자동 인증서 생성 메커니즘(DOCKER_TLS_CERTDIR)과 결합됩니다.
DOCKER_TLS_CERTDIR가 설정되면 DinD는 서버 인증서를 만들고, 클라이언트는 해당 CA를 신뢰해야 합니다.- GitLab CI에서는 보통
/certs볼륨을 공유해 Job 컨테이너가 클라이언트 인증서를 읽도록 구성합니다.
문제는 다음 중 하나라도 어긋나면 TLS가 바로 깨진다는 점입니다.
- Job이 바라보는
DOCKER_HOST가 틀림(서비스 alias, 포트) - TLS 켠 상태인데 클라이언트 인증서가 없음(볼륨 공유 실패)
- TLS 끈 상태인데 클라이언트가 TLS로 접속함(포트, 환경변수 불일치)
- Runner가
privileged를 허용하지 않아 DinD 데몬 자체가 비정상
에러 메시지 패턴별 원인과 해결
1) Cannot connect to the Docker daemon + tcp://docker:2376
대표 로그 예시(인라인 코드로 확인):
Cannot connect to the Docker daemon at tcp://docker:2376. Is the docker daemon running?dial tcp: lookup docker: no such host
원인
- 서비스 alias가
docker가 아닌데DOCKER_HOST를tcp://docker:2376로 고정 services가 누락됐거나,docker:dind가 기동 실패- 네임해석이 안 되는 러너 네트워크 설정 문제
해결
services에 alias를 명시하고DOCKER_HOST를 그 alias로 맞춥니다.- DinD가 뜨는지
docker info전 단계에서 확인합니다.
image: docker:24
services:
- name: docker:24-dind
alias: docker
variables:
DOCKER_HOST: "tcp://docker:2376"
DOCKER_TLS_CERTDIR: "/certs"
before_script:
- docker version
- docker info
build:
stage: build
script:
- docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA" .
만약 lookup docker: no such host가 나오면 alias가 제대로 적용되지 않은 경우가 많습니다. alias: docker를 명시하고, DOCKER_HOST도 일치시키세요.
2) x509: certificate signed by unknown authority
대표 로그 예시:
x509: certificate signed by unknown authoritytls: failed to verify certificate: x509: ...
원인
- TLS는 켜져 있는데, Job 컨테이너가 DinD가 만든 CA를 못 읽음
/certs볼륨 공유가 안 됨(러너 executor, 볼륨 정책)DOCKER_CERT_PATH가 틀림
해결 A: GitLab 권장 방식(TLS 유지)으로 정렬
TLS를 유지하려면 아래 3가지를 세트로 맞춰야 합니다.
DOCKER_TLS_CERTDIR는/certsDOCKER_HOST는tcp://docker:2376- Job이
DOCKER_CERT_PATH를/certs/client로 바라봄
image: docker:24
services:
- name: docker:24-dind
alias: docker
variables:
DOCKER_HOST: "tcp://docker:2376"
DOCKER_TLS_CERTDIR: "/certs"
DOCKER_CERT_PATH: "/certs/client"
DOCKER_TLS_VERIFY: "1"
before_script:
- docker info
build:
script:
- docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA" .
여기서 핵심은 DOCKER_TLS_VERIFY를 명시해 “나는 TLS로 붙는다”를 분명히 하는 것입니다. 환경변수가 애매하면 CLI가 다른 경로로 동작하면서 인증서 검증이 꼬일 수 있습니다.
해결 B: TLS를 끄고 2375로 단순화(내부망 전제)
보안적으로는 덜 권장되지만, 내부 러너 네트워크에서만 쓰고 빠르게 안정화가 필요하면 TLS를 끄는 것이 가장 단순합니다.
DOCKER_TLS_CERTDIR를 빈 문자열로DOCKER_HOST를tcp://docker:2375
image: docker:24
services:
- name: docker:24-dind
alias: docker
variables:
DOCKER_HOST: "tcp://docker:2375"
DOCKER_TLS_CERTDIR: ""
before_script:
- docker info
build:
script:
- docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA" .
운영 정책상 TLS를 꺼도 되는지(공유 러너, 멀티테넌트 여부)를 먼저 확인하세요.
3) client sent an HTTP request to an HTTPS server
대표 로그 예시:
client sent an HTTP request to an HTTPS server
원인
- DinD는 TLS(HTTPS,
2376)로 떠 있는데, Job이 HTTP(2375)로 접속 - 즉,
DOCKER_HOST와DOCKER_TLS_CERTDIR조합이 서로 충돌
해결
- TLS를 쓸 거면
DOCKER_HOST를tcp://docker:2376로, TLS를 끌 거면DOCKER_TLS_CERTDIR를""로 맞춥니다.
잘 되는 조합은 아래 둘 중 하나뿐이라고 생각하면 됩니다.
- TLS ON:
DOCKER_HOST=tcp://docker:2376+DOCKER_TLS_CERTDIR=/certs - TLS OFF:
DOCKER_HOST=tcp://docker:2375+DOCKER_TLS_CERTDIR=""
4) tls: first record does not look like a TLS handshake
대표 로그 예시:
tls: first record does not look like a TLS handshake
원인
- 위와 반대 케이스가 대부분입니다.
- DinD는 HTTP(
2375)인데, Job이 TLS로 접속(2376또는DOCKER_TLS_VERIFY=1)
해결
- TLS OFF 구성이라면
DOCKER_TLS_VERIFY를 제거하고,DOCKER_HOST를tcp://docker:2375로 고정하세요.
5) DinD 데몬이 안 뜨는 케이스(privileged 누락)
표면적으로는 TLS 에러처럼 보이지만, 실제로는 DinD가 정상 기동을 못 해서 접속이 실패하는 경우가 있습니다.
의심 신호:
docker info가 타임아웃- 서비스 로그에
mount: permission denied,failed to start daemon류 메시지
해결
GitLab Runner가 Docker executor를 쓴다면 DinD는 보통 privileged가 필요합니다.
- 러너 설정에서
privileged = true활성화 - 또는 프로젝트/그룹 러너 정책에서 허용
.gitlab-ci.yml만으로 해결이 안 되고 러너 레벨 설정이 필요한 전형적인 케이스입니다.
실전: GitLab Container Registry 로그인까지 포함한 안정 템플릿
아래는 TLS ON(권장) 기준으로, 레지스트리 로그인부터 빌드/푸시까지 한 번에 정리한 예제입니다.
stages: [build]
image: docker:24
services:
- name: docker:24-dind
alias: docker
variables:
DOCKER_HOST: "tcp://docker:2376"
DOCKER_TLS_CERTDIR: "/certs"
DOCKER_CERT_PATH: "/certs/client"
DOCKER_TLS_VERIFY: "1"
DOCKER_DRIVER: overlay2
before_script:
- docker version
- docker info
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
build-and-push:
stage: build
script:
- docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA" .
- docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA"
체크포인트
docker info가 성공해야 이후 TLS/로그인 문제를 분리해서 볼 수 있습니다.DOCKER_CERT_PATH는 반드시"/certs/client"여야 합니다."/certs"로 잡으면 클라이언트 키/인증서 위치가 어긋납니다.
자주 놓치는 디테일 6가지
1) docker:dind 이미지 태그를 고정하지 않음
docker:latest나 docker:dind를 그대로 쓰면 어느 날 TLS 기본값이나 엔트리포인트 동작이 바뀌어 깨질 수 있습니다. 최소한 메이저 버전은 고정하세요.
- 예:
docker:24와docker:24-dind
2) 서비스 alias 미지정
GitLab CI의 서비스 호스트네임은 이미지 이름이 아니라 alias로 안정화하는 편이 좋습니다.
- 권장:
alias: docker
3) DOCKER_HOST를 하드코딩해놓고 TLS 설정만 바꿈
TLS를 끄면서 DOCKER_TLS_CERTDIR만 ""로 바꾸고 DOCKER_HOST를 2376에 둔 채로 두면 100% 실패합니다.
4) 회사 프록시/미들박스가 TLS를 간섭
사내 네트워크에서 프록시가 TLS를 가로채면 x509가 이상한 형태로 발생할 수 있습니다. 이 경우는 DinD 인증서 문제가 아니라 네트워크 경로 문제일 수 있으니, 동일 러너에서 외부 API 인증이 깨지는지도 같이 확인해보세요(점검 접근은 OpenAI Responses API 401 403 인증오류 점검 가이드 흐름과 유사합니다).
5) Kubernetes executor에서 DinD를 무리하게 사용
Kubernetes executor에서는 DinD가 보안/성능/네트워크 면에서 까다롭습니다. 가능하면 Kaniko, BuildKit(rootless), Docker socket 방식 등 대안을 검토하세요. (대용량 요청/프록시/ingress 이슈를 겪고 있다면 Kubernetes API 413 Request Entity Too Large 해결 같은 네트워크 레이어 점검 글도 같이 보면 좋습니다.)
6) 문제를 “TLS”로만 단정하고 로그를 안 나눔
docker info 성공 여부를 기준으로 계층을 분리하세요.
docker info실패: DinD 기동/네트워크/privileged 문제docker info성공 +docker login실패: 레지스트리 인증/CA/프록시 문제docker build만 실패: 빌드 컨텍스트, 네트워크, 레이어 캐시, 디스크 문제
문제 해결을 빠르게 만드는 10분 진단 루틴
아래 순서대로 실행하면, TLS 문제를 “정확히 어디에서 깨지는지” 10분 내로 분리할 수 있습니다.
services에docker:dind가 있는지 확인- alias를
docker로 고정했는지 확인 DOCKER_HOST가2375인지2376인지 확인- TLS ON이면
DOCKER_TLS_CERTDIR=/certs,DOCKER_CERT_PATH=/certs/client,DOCKER_TLS_VERIFY=1확인 - TLS OFF이면
DOCKER_TLS_CERTDIR=""확인 before_script에docker info추가- 실패 시 러너에서
privileged허용 여부 확인 - 프록시 환경변수(
HTTP_PROXY,HTTPS_PROXY,NO_PROXY)가 있다면NO_PROXY에dockeralias를 포함
NO_PROXY 예시는 다음처럼 인라인 코드로 관리하세요: NO_PROXY=localhost,127.0.0.1,docker
마무리
GitLab CI의 DinD TLS 실패는 “인증서가 나빠서”라기보다, 대부분 TLS ON/OFF 조합 불일치, 서비스 alias/포트 불일치, certs 볼륨 공유 실패, privileged 누락 같은 구성 문제에서 시작합니다.
가장 안정적인 운영 전략은 다음 중 하나를 선택해 일관되게 고정하는 것입니다.
- 보안 우선: TLS ON 조합을 표준 템플릿으로 고정
- 단순 우선(내부망): TLS OFF로 단순화하되 러너 격리/권한을 강화
CI 파이프라인에서 변경이 잦다면, 템플릿을 중앙화하고 태그를 고정해 “업데이트로 인한 우발적 TLS 붕괴”를 줄이는 것이 장기적으로 가장 큰 비용 절감 포인트입니다.