Docker에서 MySQL 연결 안 될 때 해결법
제가 실무를 하면서 RDS를 사용하지 않고 EC2에서 Docker를 이용하는 경우는 대부분 RDS를 쓰자니 금액적인 부분이 걸려서 혹은 서비스의 범위가 크지 않은 경우에 EC2에서 Docker를 이용해 DB를 올리는 경우가 많았습니다.
그 과정에서 발생했던 자주보였던 이슈에 대해 오늘은 공부해 보려고 합니다. 연결에 관한 부분은 RDMS가 어떤것인지에 상관없이 공통적으로 나타나는 부분이므로 참고하여 알아두면 좋을것 같습니다.
1. Docker MySQL 연결 안 되는 대표적인 3가지 원인
| 원인 구분 | 설명 | 대표 에러 메시지 |
|---|---|---|
| 1️⃣ 네트워크 문제 | 컨테이너 간 네트워크 미연결 또는 호스트 접근 불가 | ECONNREFUSED, Connection timed out |
| 2️⃣ MySQL 설정 문제 | bind-address, 사용자(host) 권한 문제 |
Host not allowed to connect |
| 3️⃣ 환경 변수 / 포트 문제 | .env 오타, 포트 중복, MYSQL_ROOT_PASSWORD 누락 |
Access denied, Connection refused |
기본 구조 예시 (정상 연결 기준)
version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: mydb
restart: always
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: testdb
MYSQL_USER: testuser
MYSQL_PASSWORD: testpass
ports:
- "3306:3306"
volumes:
- ./data/mysql:/var/lib/mysql
networks:
- app-net
app:
build: .
depends_on:
- mysql
environment:
DB_HOST: mysql
DB_PORT: 3306
DB_USER: testuser
DB_PASSWORD: testpass
networks:
- app-net
networks:
app-net:
driver: bridge
- 핵심 포인트
app과mysql이 같은 네트워크(app-net)에 있어야 함- 애플리케이션에서 DB 접근 시
localhost대신 서비스명(mysql) 사용해야 함DB_HOST=mysql # X: localhost → O: mysql
이 두가지는 docker의 특성인데 docker에서 컨테이너를 동작시킬때 컨테이너 내부ip는 재기동시 바뀔 가능성이 높습니다. 이러한 불편함을 없애는 방법은 같은 네트워크에 등록하면 서비스명으로 해당 컨테이너에 접근하는 것 입니다. 이 방법은 docker로 mysql을 연동하는 것 뿐만이 아니라 컨테이너를 이용하는 서비스들 간에서는 동일하게 적용이 가능합니다.
2. 원인별 실전 해결 가이드
2.1 네트워크 연결 문제
- 문제 증상
- 다른 컨테이너에서 MySQL 접근 불가
Connection timed out또는ECONNREFUSED
- 원인
- 컨테이너 간 네트워크 미연결
- 외부 호스트에서 접근 시 포트 미노출
- 해결 방법
- 컨테이너 간 접근 시
docker exec -it app ping mysql # ping이 되지 않으면 같은 network에 없다는 뜻
- 같은 network 연결 확인
docker network lsdocker network inspect app-net
- 외부 접속 허용 (로컬에서 접근 시)
ports: - "3306:3306"
- 컨테이너 간 접근 시
2.2 MySQL 설정 문제 (bind-address, 사용자 권한)
- 문제 증상
- MySQL 컨테이너 실행은 정상인데 외부 접속 실패
- 에러:
Host '172.18.0.5' is not allowed to connect to this MySQL server
- 에러:
- MySQL 컨테이너 실행은 정상인데 외부 접속 실패
- 원인
- MySQL이 내부 IP(
127.0.0.1)만 바인딩 - 해당 호스트가 접근 권한(
GRANT)이 없음
- MySQL이 내부 IP(
- 해결 방법
- bind-address 확인
docker exec -it mydb bash cat /etc/mysql/my.cnf | grep bind-address- 기본 설정이
127.0.0.1이라면 아래처럼 수정합니다. # /etc/mysql/my.cnf [mysqld] bind-address=0.0.0.0
- 사용자 접근 권한 추가
#root 권한 접속 mysql -u root -p # 모든 호스트에서 testuser로 접속 허용 GRANT ALL PRIVILEGES ON _._ TO 'testuser'@'%' IDENTIFIED BY 'testpass'; FLUSH PRIVILEGES; - Dockerfile로 자동 적용하려면
# custom.cnf [mysqld] bind-address=0.0.0.0#Dockerfile FROM mysql:8.0 COPY ./custom.cnf /etc/mysql/conf.d/custom.cnf
- bind-address 확인
2.3 환경 변수 / 포트 관련 문제
- 문제 증상
- 컨테이너는 정상인데 접속 시
Access denied=>MYSQL_ROOT_PASSWORD가 없거나.env값 불일치
- 컨테이너는 정상인데 접속 시
- 해결 방법
- 환경 변수 일치 확인
docker-compose config # 실제 적용된 env 값 확인 가능 - 포트 충돌 확인
sudo lsof -i :3306 - 호스트 PC에서 MySQL 접속 테스트
mysql -h 127.0.0.1 -P 3306 -u testuser -p
- 환경 변수 일치 확인
3. 실전 트러블슈팅 케이스
케이스 1: Spring Boot에서 "Connection refused"
spring: datasource: url: jdbc:mysql://mysql:3306/testdb?useSSL=false username: testuser password: testpass
해결: localhost → mysql 변경 (같은 네트워크 내 DNS 이름)
케이스 2: “Host not allowed to connect”
mysql> SHOW GRANTS FOR 'testuser'@'%';
없다면 아래 실행:
GRANT ALL PRIVILEGES ON *.* TO 'testuser'@'%' IDENTIFIED BY 'testpass'; FLUSH PRIVILEGES;
케이스 3: “Too many connections”
max_connections설정을 늘려야 함
SET GLOBAL max_connections = 500;
또는 Dockerfile 내 설정:
[mysqld] max_connections=500
4. 실전 적용
4.1 최소 docker-compose 템플릿
version: "3.8"
services:
mysql:
image: mysql:8.0
container_name: mysql8
restart: always
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: appdb
MYSQL_USER: appuser
MYSQL_PASSWORD: apppass
ports:
- "3306:3306"
volumes:
- ./data/mysql:/var/lib/mysql
- ./init:/docker-entrypoint-initdb.d # (옵션) 초기 스키마/데이터
healthcheck:
# 중요: $$ 로 이스케이프 (Compose의 env 치환 방지)
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -p$$MYSQL_ROOT_PASSWORD --silent"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s # 초기 InnoDB 리커버리/DDL 시간 버퍼
app:
build: .
container_name: app
depends_on:
mysql:
condition: service_healthy # DB healthy 될 때까지 app 시작 대기
environment:
DB_HOST: mysql
DB_PORT: 3306
DB_NAME: appdb
DB_USER: appuser
DB_PASSWORD: apppass
restart: on-failure
포인트
healthcheck.start_period: 초기화(디스크 복구/DDL) 동안 false-negative 방지.retries/interval튜닝으로 초기 1~3분 지연도 흡수 가능.- 앱은
DB_HOST=mysql(서비스명 DNS) 사용.localhost사용 불가
4.2 Spring Boot 연결 예시
application.yml
spring:
datasource:
url: jdbc:mysql://mysql:3306/appdb?useSSL=false&allowPublicKeyRetrieval=true
username: ${DB_USER}
password: ${DB_PASSWORD}
sql:
init:
mode: always # (선택) /docker-entrypoint-initdb.d 와 충돌되지 않게 조정
jpa:
hibernate:
ddl-auto: none
# 연결 재시도/타임아웃(드라이버/풀 옵션) 예시
datasource.hikari:
initializationFailTimeout: 0
connectionTimeout: 10000
validationTimeout: 5000
maximumPoolSize: 10
실무 팁: 초기 접속 실패 시 앱이 바로 죽지 않게
initializationFailTimeout=0로 지연 허용, 재시도 로직(백오프)도 있으면 안정적.
5. 자주 터지는 함정 & 해결
- healthcheck가 계속 Unhealthy
- 비번 오타/이스케이프 미적용:
-p$$MYSQL_ROOT_PASSWORD인지 확인 - MySQL이 아직 초기화 중:
start_period↑ /retries↑ - 컨테이너 내부에서만 체크해야 하므로
-h 127.0.0.1사용 유지
- 비번 오타/이스케이프 미적용:
- 앱이 DB Healthy 전부터 실행됨
- Compose(로컬)는 위 예제가 OK.
- Swarm/stack에선
depends_on.condition미지원 → wait-for-it/dockerize로 대기 스크립트 사용.
(A) wait-for-it.sh
# 앱 Dockerfile 일부
ADD https://raw.githubusercontent.com/eficode/wait-for/v2.2.4/wait-for /usr/local/bin/wait-for
RUN chmod +x /usr/local/bin/wait-for
CMD ["sh", "-c", "wait-for mysql:3306 -t 120 -- java -jar app.jar"]
- 장점: 간단, 어디서나 동작
- 단점: 포트 열림만 확인(인증/권한까지는 아님)
(B) dockerize
ADD https://github.com/jwilder/dockerize/releases/download/v0.7.0/dockerize-linux-amd64-v0.7.0.tar.gz /tmp/
RUN tar -C /usr/local/bin -xzf /tmp/dockerize-linux-amd64-v0.7.0.tar.gz
CMD ["sh","-c","dockerize -wait tcp://mysql:3306 -timeout 2m && java -jar app.jar"]
- 장점: 템플릿/대기 등 기능 풍부
- 단점: 바이너리 추가 필요
권장 조합: 로컬/Compose → healthcheck + depends_on. Swarm/쿠버/배포 → 대기 스크립트 + 앱 재시도 함께.
- 스키마가 없어서 앱 부팅 실패
/docker-entrypoint-initdb.d에 DDL/데이터 넣어 초기 스키마 보장- 또는 앱의 DB 마이그레이션 도구(Flyway/Liquibase) 사용
- 호스트에서 연결 안 됨
ports: "3306:3306"확인- 맥/윈도WSL에서는 방화벽/포트 충돌 확인
6. 상태 확인 & 디버깅 명령 모음
# 컨테이너 상태/헬스
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
# 특정 서비스 헬스 필드 확인
docker inspect --format='{{json .State.Health}}' mysql8 | jq .
# 로그 확인
docker logs -f mysql8
docker logs -f app
# 네트워크 점검
docker exec -it app ping -c1 mysql
docker exec -it app getent hosts mysql
7. 프로덕션 안정성 높이기 위한 팁
- healthcheck를 과도하게 촘촘히 하지 않기: 2–10초 간격, retries 5–10 수준 권장(리소스 절약)
- MySQL 준비 신호 강화: 단순 ping 대신 “특정 DB/테이블 존재” SQL을 테스트하는 스크립트로 커스텀 가능
- 앱 레벨 Circuit Breaker/Retry: 일시적 DB 장애에 서비스 전체가 멈추지 않도록 보호
- 관측성: 헬스 상태 변화, 재시작 횟수, 초기화 시간(DDL/리커버리)을 로그/메트릭에 기록
- 데이터 초기화 충돌 방지:
/docker-entrypoint-initdb.d와 애플리케이션 마이그레이션 도구의 역할 분리를 명확히
'Backend > Study' 카테고리의 다른 글
| [Study] 백엔드 로그 관리 전략 (ELK, Loki, Grafana) (0) | 2025.10.14 |
|---|---|
| [Study] 웹소켓 vs SSE(Server-Sent Events) 차이와 활용법 (0) | 2025.10.13 |
| [Study] JWT 활용 및 예제 코드 (0) | 2025.10.03 |
| [Study] JWT 동작방식 깊게 살펴보기 (0) | 2025.10.02 |
| [Study] JWT 토큰 인증 방식과 보안 고려사항 (0) | 2025.10.01 |