Access denied for user 'root'@'localhost' (using password: YES)

ChoRong0824·2025년 9월 20일

수정하고,

compose down, up 해서 재기동 해주면 됩니다.

호스트에 깔려 있던 로컬 MySQL(mysqld)이 3306을 선점하고 있어서, 컨테이너 DB와 앱 연결이 계속 꼬였던 것.
docker compose down -v로 초기화하고, 포트 충돌을 피하도록 설정을 정리(혹은 3306 점유 해제)하니 정상 동작.

Docker + Spring Boot 로컬 DB 연결 삽질기: Access denied → Connection refused → 해결

TL;DR

  • 증상: Access denied for user 'root'@'localhost', 이어서 Connection refused (localhost:3307)
  • 원인:
    1) MariaDB 초기화 비밀번호가 컨테이너에 전달되지 않음(환경변수/볼륨 이슈)
    2) 로컬 MySQL이 3306 포트를 선점 → 컨테이너 DB와 포트 충돌
  • 해결:
    • .envDocker Compose가 읽도록 하고(down -v → up -d)
    • 컨테이너 포트를 3307로 변경(또는 로컬 MySQL 중지)
    • 애플리케이션의 jdbc url컨테이너 노출 포트와 일치시키기

환경

  • Spring Boot 3.5.6 (Java 17+)
  • MariaDB / Redis 도커 컨테이너
  • macOS + Docker Desktop
  • IntelliJ IDEA (Run/Debug로 앱 기동)

증상 타임라인

1) 도커 태그 오류
이메일 주소로 이미지 태깅 → invalid reference format (도커 태그에는 @ 불가)

2) 컨테이너 즉시 종료
MariaDB 로그: Database is uninitialized and password option is not specified
원인: MARIADB_ROOT_PASSWORDentrypoint에 잘못 넣거나, .env 값이 Compose로 전달되지 않음

3) Access denied (1045)
앱이 root로 접속, DB 초기 비번/정책 불일치

4) Connection refused (3307)
포트를 바꿨지만 접속 거절. lsof -i :3306 결과 로컬 mysqld가 3306 선점 확인 → 포트 충돌

5) 정리 후 성공
Compose와 앱 설정을 같은 포트/계정으로 정렬하고, down -v로 볼륨 초기화 → 정상 기동


왜 이런 문제가 생겼나 (사고 과정)

  • 환경변수 스코프 착각: IntelliJ Run/Debug의 env ≠ Docker Compose의 .env
  • MariaDB 초기화 특성: 한 번 생성된 볼륨에 초기 비번/스키마 고정 → 비번 변경 시 down -v 필수
  • 포트 충돌: 로컬 설치 DB와 도커 DB가 같은 3306 사용 → lsof로 즉시 확인해야 함
  • 설정 불일치: Compose(3307) ↔ 애플리케이션 URL(3306) 엇갈림, 계정/비번 불일치가 누적

최종 설정 (작동 버전)

.env (깃에 올리지 않기)

DB_ROOT_PASSWORD=1234
DB_USER=pharmacy
DB_USER_PASSWORD=pharmapw

docker-compose-local.yml

version: "3.8"

services:
  pharmacy-recommendation-database:
    image: mariadb:11.4
    container_name: pharmacy-recommendation-database
    environment:
      MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MARIADB_DATABASE: pharmacy-recommendation
      MARIADB_USER: ${DB_USER}
      MARIADB_PASSWORD: ${DB_USER_PASSWORD}
      TZ: Asia/Seoul
    ports:
      - "3307:3306"   # 호스트 3307 → 컨테이너 3306
    volumes:
      - ./database/config:/etc/mysql/conf.d
      - ./database/init:/docker-entrypoint-initdb.d

  pharmacy-recommendation-redis:
    image: redis:7-alpine
    container_name: pharmacy-recommendation-redis
    ports:
      - "6379:6379"

application.yml (local 프로필)

spring:
  config:
    activate:
      on-profile: local

  datasource:
    url: jdbc:mariadb://localhost:3307/pharmacy-recommendation
    username: ${DB_USER}
    password: ${DB_USER_PASSWORD}
    driver-class-name: org.mariadb.jdbc.Driver

  jpa:
    hibernate:
      ddl-auto: validate   # 개발 초기엔 update/create 선택 가능
    properties:
      hibernate.dialect: org.hibernate.dialect.MariaDBDialect

  data:
    redis:
      host: localhost
      port: 6379

실행 순서 (재현 가능한 루틴)

# (선택) 로컬 MySQL이 3306 점유 중인지 확인
lsof -i :3306 -nP

# Compose가 .env를 제대로 읽는지 치환 결과 확인
docker compose --env-file ./.env -f docker-compose-local.yml config | grep -A2 MARIADB_ROOT_PASSWORD

# 완전 초기화 후 재기동 (비번/스키마 반영)
docker compose --env-file ./.env -f docker-compose-local.yml down -v
docker compose --env-file ./.env -f docker-compose-local.yml up -d

# 포트 매핑 확인 (3307 → 3306)
docker ps --format 'table {{.Names}}\t{{.Ports}}'

# DB 준비 상태 확인
docker logs --tail=100 pharmacy-recommendation-database | tail -n 20  # "ready for connections"

# 호스트에서 직접 접속 테스트(가장 확실)
mysql -h 127.0.0.1 -P 3307 -u ${DB_USER} -p${DB_USER_PASSWORD} -e "SELECT 1;"

로컬 DB 연결 체크리스트 (Docker + Spring Boot)

  • 포트 충돌 확인
    lsof -i :3306 -nP (충돌 시 3307 등으로 변경하거나 로컬 MySQL 중지)

  • Compose ↔ 앱 포트 일치
    compose: ports: ["3307:3306"] ↔ app: jdbc:mariadb://localhost:3307/...

  • .env가 Compose에 주입되는지 확인
    docker compose --env-file ./.env -f docker-compose-local.yml config | grep -A2 MARIADB_ROOT_PASSWORD

  • 초기화 값 변경 시 볼륨 제거
    docker compose --env-file ./.env -f docker-compose-local.yml down -v

  • 재기동 & 상태 점검
    docker compose --env-file ./.env -f docker-compose-local.yml up -d
    docker ps --format 'table {{.Names}}\t{{.Ports}}' (예: 0.0.0.0:3307->3306/tcp)

  • DB 준비 여부 확인
    docker logs --tail=100 pharmacy-recommendation-database (→ ready for connections)

  • 호스트에서 직접 접속 테스트
    mysql -h 127.0.0.1 -P 3307 -u <USER> -p<PW> -e "SELECT 1;"

  • 애플리케이션 계정/비번 일치
    app username/password = compose의 MARIADB_USER/MARIADB_PASSWORD (또는 root 사용 시 동일 비번)

  • root로 접속 시 정책 허용(선택)
    compose에 MARIADB_ROOT_HOST: "%" 추가(보안상 권장 X)

  • Spring Boot 3.x Redis 키 사용
    spring.data.redis.host, spring.data.redis.port

  • 환경변수 스코프 분리
    Compose는 .env, 앱은 Run/Debug env 또는 쉘 export로 주입

  • 도커 이미지 태그 규칙
    이메일/@ 금지, (형식: <repo>/<name>:<tag>)


배운 점

  • Access denied ↔ Connection refused는 원인이 다르다
    - Access denied = 계정/권한/비번
    - Connection refused = 포트/서비스 미기동/방화벽
  • 네트워크 이슈는 앱 로그 전에 lsof, docker ps, mysql -P 로 단위 진단
  • 환경변수 스코프를 분리해서 생각하자: Compose(.env) vs 앱(Run/Debug env)

profile
정진, "어제보다 더 나은 오늘이 되자"

0개의 댓글