증상: 프론트에서 Google 로그인 버튼 클릭 → 브라우저가 http://localhost:8080/oauth2/authorization/google로 이동했지만 ERR_CONNECTION_REFUSED.
원인: 백엔드 컨테이너가 MySQL에 연결 실패하여 부팅 직후 재시작 루프. 컨테이너 내부에서 DB_HOST=localhost로 설정되어 있어 자기 자신을 가리킴 → DB에 붙지 못하고 JDBCConnectionException: Communications link failure 발생.
해결: 방법 A) 데이터베이스도 컨테이너로 함께 띄우기(권장). Compose 네트워크의 DNS 이름(서비스명)으로 연결하고, DB가 정상 기동(healthy) 된 이후에 백엔드가 뜨도록 healthcheck + depends_on 구성.
http://localhost:5173)parkjihyeon/linkrew-server:latest)로그인 버튼 클릭 → http://localhost:8080/oauth2/authorization/google 접속 시도 → 브라우저 연결 거부.
docker ps 확인 시 백엔드 컨테이너 Restarting (...) 상태.
docker logs linkrew-server 핵심 로그:
org.hibernate.exception.JDBCConnectionException: Unable to open JDBC Connection ...
com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure
java.net.ConnectException: Connection refused
→ DB에 접속 자체가 안 됨.
컨테이너 내부의 localhost 오해
localhost는 해당 컨테이너 자신을 의미.DB_HOST는 localhost가 아니라 Compose 서비스명(예: linkrew-database)이어야 함.DB 서비스가 Compose에 없음 또는 준비 전 접속 시도
healthcheck + depends_on: condition: service_healthy 필요.볼륨 삭제 습관(down -v)
-v는 DB 데이터를 완전히 초기화. 매번 계정/스키마가 사라질 수 있음. (재현·테스트 용도 외엔 지양).env.local핵심:
DB_HOST=linkrew-database,DB_PORT=3306(컨테이너 내부 포트 기준)으로 변경
# Spring
ENV_FILE=.env.local
SPRING_PROFILE=local
SPRING_PROFILES_ACTIVE=local
SPRING_PORT=8080
# Database (컨테이너-컨테이너 연결)
DB_HOST=linkrew-database
DB_PORT=3306
DB_DATABASE=linkrew_db
DB_USERNAME=linkrew-user
DB_PASSWORD=linkrew1234
DB_URL=jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_DATABASE}?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Seoul
# JWT
JWT_SECRET=...적절한_값...
# OAuth2 Redirects (백엔드가 8080에 노출된다는 가정)
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
GOOGLE_REDIRECT_URI=http://localhost:8080/login/oauth2/code/google
KAKAO_CLIENT_ID=...
KAKAO_CLIENT_SECRET=...
KAKAO_REDIRECT_URI=http://localhost:8080/login/oauth2/code/kakao
# Front URL
FRONTEND_BASE_URL=http://localhost:5173
참고: 호스트(맥)에서 DB에 직접 붙고 싶다면 외부 포트를 예: 3307로 노출하고, 로컬 클라이언트는
localhost:3307로 접속하면 됨. 그러나 백엔드 컨테이너가 DB에 붙을 때는 항상 내부 포트(3306)와 서비스명을 사용.
docker-compose.yml핵심: DB 서비스 추가, 헬스체크, 백엔드
depends_on: service_healthy, 컨테이너 이름 하드코딩 제거
version: '3.8'
services:
linkrew-database:
image: mysql:8.0
environment:
MYSQL_DATABASE: linkrew_db
MYSQL_USER: linkrew-user
MYSQL_PASSWORD: linkrew1234
MYSQL_ROOT_PASSWORD: rootpass
ports:
- "3307:3306" # 호스트에서 접속할 때만 사용(선택). 컨테이너 간 연결엔 3306 사용.
volumes:
- linkrew-mysql:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-prootpass"]
interval: 5s
timeout: 3s
retries: 30
linkrew-server:
image: parkjihyeon/linkrew-server:latest
env_file:
- ${ENV_FILE}
# 우변(컨테이너 내부 포트)은 보통 8080 (이미지에 따라 다르면 로그로 확인)
ports:
- "${SPRING_PORT}:8080"
restart: always
depends_on:
linkrew-database:
condition: service_healthy
volumes:
linkrew-mysql:
팁
container_name:는 가급적 지양. 폴더·프로젝트별 자동 네이밍이 충돌을 줄여준다.- 내부 포트가 8080이 아닐 수 있음. 백엔드 로그에서
Tomcat started on port(s)줄을 확인해 우변 숫자를 맞춰라.
# 0) 기존 리소스 정리 (데이터 보존을 원하면 -v 쓰지 않기)
docker compose down --remove-orphans
# 1) 재기동
docker compose --env-file .env.local up -d --build
# 2) 상태 확인
docker compose ps
# 3) 로그 확인 (DB → healthy, BE → 에러 없이 기동)
docker logs -n 200 linkrew-database
docker logs -n 200 linkrew-server
# 4) 헬스체크/엔드포인트 확인
curl -i http://localhost:8080/actuator/health
정상이면:
linkrew-database는 healthy.linkrew-server는 Restarting 아님, 0.0.0.0:8080->8080/tcp 바인딩.http://localhost:8080/oauth2/authorization/google 접근 시 더 이상 ECONNREFUSED 없음.컨테이너 네트워크: 같은 Compose 네트워크의 서비스끼리는 서비스명으로 DNS가 자동 구성됨. linkrew-server → linkrew-database:3306으로 접속.
포트 매핑: 호스트:컨테이너 형태.
3307:3306으로 노출했더라도, 컨테이너-컨테이너 간에는 항상 컨테이너 포트(3306) 를 사용.기동 순서: DB 준비 없이 BE가 먼저 뜨면 접속 실패. healthcheck + depends_on(healthy)로 완화.
.env에서 DB_HOST=localhost로 남아있다 → 서비스명으로 바꿔라.3307로 노출했는데, BE가 그 포트로 붙는다 → 잘못. BE는 3306으로.container_name 하드코딩 → 폴더/프로젝트 간 이름 충돌. 지우는 게 안전.down -v → 데이터 날림. 특별한 이유 없으면 쓰지 말기.ports: "${SPRING_PORT}:${SPRING_PORT}" → 내부 포트가 8080이 아닐 수 있다. 보통 ${SPRING_PORT}:8080 이 안전.ERR_CONNECTION_REFUSED 나오면 우선 docker ps, docker logs에서 포트 바인딩/재시작 여부 확인.DB가 healthy가 안 됨
docker logs linkrew-databaseretries) 증가.포트 충돌
lsof -i :8080 으로 사용 프로세스 확인. 다른 앱이 쓰면 호스트 포트 변경.로컬 DB로 붙고 싶다
DB_HOST=host.docker.internal, DB_PORT=로컬포트. (이번 해결에는 방법 A 사용)초기 데이터/계정 필요
down -v 후엔 데이터 소멸. 필요한 경우 init SQL 또는 앱 시드 로직 적용.이번 이슈는 컨테이너 네트워킹 모델과 기동 시점 문제였다. DB를 Compose에 포함시키고, 서비스명으로 붙이며, healthcheck로 준비 완료를 보장하자. 이렇게 정리하면 로그인(OAuth 포함) 플로우가 안정적으로 복구된다.
필요하면 위 설정을 기반으로 프로파일(로컬/스테이징/프로덕션) 분리, 시드 스크립트 자동화, 모니터링(Loki/Grafana) 까지 확장하면 된다. 다음은 재발 방지용 원클릭 스크립트 예시.
#!/usr/bin/env bash
set -euo pipefail
# 0) 안전 정리 (데이터 유지)
docker compose down --remove-orphans || true
# 1) 재기동
docker compose --env-file .env.local up -d --build
# 2) 상태 체크
docker compose ps
sleep 2
curl -sS http://localhost:8080/actuator/health || echo "health endpoint not ready yet"
여기까지 적용하면, 로그인 버튼 → 정상 동작. 끝.