에러메세지 / 문제상황
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
Reason: Failed to determine suitable jdbc url
백엔드 개발자들은 많이 보는 에러일 것이다.
springboot 서버를 실행할 때, 설정파일 (application.yaml)을 제대로 못읽었을 때 발생하는 에러이다..
원인
Spring Boot 프로젝트에서 데이터베이스 연결 정보를 application.yml과 application-secret.yml 두 개 파일로 나눠 관리 중이었음. Jenkins를 이용해 application-secret.yml을 credentials에서 복사해 프로젝트 경로에 넣어줬으나, Spring Boot가 실제 실행 시 ${}로 선언된 환경변수를 JVM 또는 컨테이너 환경에서 찾지 못해 데이터베이스 연결 정보가 누락되어 발생한 문제.
application.yml (기존)
server:
servlet:
context-path: /api/v1
port: ${SPRING_CONTAINER_PORT}
spring:
application:
name: fourcut
config:
import: "optional:classpath:application-secret.yml"
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: ${DB_URL}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
jpa:
database-platform: org.hibernate.dialect.MySQLDialect
hibernate:
ddl-auto: update # none, update, create, create-drop
properties:
hibernate:
show_sql: true
format_sql: true
use_sql_comments: true
dialect: org.hibernate.dialect.MySQLDialect
security:
oauth2:
client:
registration:
kakao:
client-id: ${KAKAO_CLIENT_ID}
client-secret: ${KAKAO_CLIENT_SECRET}
redirect-uri: ""
authorization-grant-type: authorization_code
client-authentication-method: client_secret_post
client-name: kakao
scope:
- account_email
provider:
kakao:
authorization-uri: <https://kauth.kakao.com/oauth/authorize>
token-uri: <https://kauth.kakao.com/oauth/token>
user-info-uri: <https://kapi.kakao.com/v2/user/me>
user-name-attribute: id
kakao:
logout-redirect-uri:
jwt:
secret: ${JWT_SECRET}
access:
expiration: ${JWT_ACCESS_EXPIRATION}
refresh:
expiration: ${JWT_REFRESH_EXPIRATION}
cloud:
aws:
credentials:
access-key: ${AWS_ACCESS_KEY}
secret-key: ${AWS_SECRET_KEY}
region:
static: ap-northeast-2
s3:
bucket: ${AWS_S3_BUCKET}
cloudfront:
domain: ${AWS_CLOUDFRONT_DOMAIN}
keyPairId: ${CLOUDFRONT_KEY_PAIR_ID}
privateKeyPath: ${CLOUDFRONT_PRIVATE_KEY_PATH}
application-secret.yml(기존)
SPRING_IMAGE_NAME: spring-project
SPRING_CONTAINER_NAME: backend
SPRING_CONTAINER_PORT: 8081
KAKAO_CLIENT_ID: kakao_client_id
KAKAO_CLIENT_SECRET: kakao_client_secret
DB_URL: jdbc:mysql://<db_url>:3306/<db_name>?serverTimezone=Asia/Seoul&characterEncoding=UTF-8
DB_USERNAME: db_username
DB_PASSWORD: db_password
JWT_SECRET: jwt_secret
JWT_ACCESS_EXPIRATION: 3600000
JWT_REFRESH_EXPIRATION: 604800000
AWS_ACCESS_KEY: aws_access_key
AWS_SECRET_KEY: aws_secret_key
AWS_S3_BUCKET: aws_s3_bucket
AWS_CLOUDFRONT_DOMAIN: aws_cloudfront_domain
CLOUDFRONT_PRIVATE_KEY_PATH: private_key.pem
CLOUDFRONT_KEY_PAIR_ID: cloudfront_key_pair_id
시도 1
- Jenkins credentials를 통해 application-secret.yml 파일을 서버에 복사한 후 바로 Spring Boot가 읽을 것이라 생각하고 환경변수 참조(${})로 작성함.
- 파일은 정상적으로 복사되었으나, Spring Boot가 해당 값을 환경변수에서 찾으려 하면서 실패함.
시도 2
- Jenkins 빌드 단계의 Execute Shell에서 export 명령어를 이용해 YAML 파일의 변수를 쉘 환경변수로 등록 시도.
- export 명령어로는 Jenkins 쉘 내에서만 변수가 유지되고, 실제 Docker 컨테이너 환경에는 전달되지 않아 Spring Boot는 여전히 환경변수를 찾지 못함.
시도 3
- docker-compose.yml 파일의 environment: 블록에 환경변수를 전달하여 컨테이너 내부에 변수를 전달하려 시도.
- Jenkins 쉘에서 export된 환경변수가 docker-compose 명령어까지 전달되지 않아 실패.
- 추가로 docker-compose.yml에 환경변수를 다 명시하게 되면, Jenkins Credentials로 파일(application-secret.yml)을 관리할 이유가 없어져 "credentials로 파일 복사"라는 설계 의도 자체가 무너진다는 문제 인식 → 이 방법을 포기함
해결책
- 환경변수(${}) 참조 방식을 사용하지 않고, application-secret.yml 파일을 "완성형" 형태로 변경하여 Spring Boot가 직접 읽고 바로 설정값을 사용할 수 있도록 변경.
- 즉, application-secret.yml에 데이터베이스 URL과 계정 정보 등 모든 민감한 정보를 하드코딩하여 직접 기입하고, 이를 spring.config.import로 application.yml에서 불러오는 구조로 변경.
application.yml
server:
servlet:
context-path: /api/v1
port: ${SPRING_CONTAINER_PORT}
spring:
application:
name: fourcut
config:
import: "optional:classpath:application-secret.yml"
application-secret.yml
BUCKET_NAME: bucket_name
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://{mysql_container_name}:3306/{db_name}?serverTimezone=Asia/Seoul&characterEncoding=UTF-8
username: mysql_username
password: mysql_password
jpa:
database-platform: org.hibernate.dialect.MySQLDialect
hibernate:
ddl-auto: update
properties:
hibernate:
show_sql: true
format_sql: true
use_sql_comments: true
dialect: org.hibernate.dialect.MySQLDialect
security:
oauth2:
client:
registration:
kakao:
client-id: kakao_client_id
client-secret: kakao_client_secret
redirect-uri: kakao_redirect_uri
authorization-grant-type: grant_type
client-authentication-method: kakao_client_authentication_method
client-name: kakao_client_name
scope:
- account_email
provider:
kakao:
authorization-uri: kakao_authorization_uri
token-uri: kakao_token_uri
user-info-uri: kakao_user_info_uri
user-name-attribute: id
kakao:
logout-redirect-uri: kakao_logout_redirect_uri
jwt:
secret: jwt_secret
access:
expiration: jwt_access_expiration
refresh:
expiration: jwt_refresh_expiration
cloud:
aws:
credentials:
access-key: aws_access_key
secret-key: aws_secret_key
region:
static: cloudfront_region_static
s3:
bucket: s3_bucket_name
cloudfront:
domain: aws_cloudfront_domain
keyPairId: aws_cloudfront_key_pair_id
privateKeyPath: aws_cloudfront_private_key_path
cookie:
domain: .film-moa.com
path: /
same-site: Strict
http-only: true
secure: ${COOKIE_SECURE:true}
회고 및 정리
- Spring Boot는 ${} 표현을 만나면 JVM 환경변수 또는 Docker 컨테이너 환경변수에서 값을 찾기 때문에, ${}를 사용하려면 docker-compose.yml의 environment 설정이나 JVM 시스템 프로퍼티로 반드시 값을 주입해야 한다는 것을 정확히 이해함.
- application-secret.yml 파일을 Jenkins Credentials로 복사만 했을 경우에는, ${} 변수 치환이 자동으로 일어나지 않는다는 점을 놓쳤고, 이는 JVM이나 Docker 컨테이너 실행 시점에 환경변수로 전달되지 않으면 해결할 수 없다는 것을 알게 됨.
- docker-compose.yml 파일에 모든 환경변수를 주입하는 방식(environment:)을 쓰려면, 결국 Jenkins Credentials에 파일(application-secret.yml)로 관리하는 의미가 없어지고, 오히려 관리 포인트가 분산되어 복잡해진다는 것을 인식함.
- 이 때문에 application.yml에서는 최소 설정(server.port, context-path 등)만 남기고, application-secret.yml은 완성된 값(하드코딩 형태)으로 작성하여 Spring Boot가 파일만 읽으면 동작하도록 구조를 바꿈.
- 결국 민감 정보 관리 방식을 파일 기반으로 통일하고, Jenkins Credentials를 통해 민감 파일을 복사하는 방식으로 간소화하면서, docker-compose.yml은 포트(SPRING_CONTAINER_PORT) 등 필수 최소값만 넘기는 구조로 정리함.
- 이번 경험을 통해, 배포 파이프라인을 설계할 때 "환경변수 기반 관리"와 "파일 기반 관리"는 서로 전략이 다르며, 둘을 섞으면 복잡성과 오류 가능성이 급격히 올라간다는 것을 체감함. 따라서 초기에 민감정보 관리 방식을 명확히 정의하는 것이 중요함을 배움.
- Jenkins credentials를 활용할 때, Spring Boot가 참조하는 방식(yml 직접 읽기 vs 환경변수 참조)을 명확히 맞춰야 하며, credentials 파일을 완성형으로 구성하면 설정과 관리가 매우 단순해지고, 트러블슈팅 시간도 대폭 줄일 수 있다는 것을 깨달음.