에러메세지 / 문제상황

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 파일을 완성형으로 구성하면 설정과 관리가 매우 단순해지고, 트러블슈팅 시간도 대폭 줄일 수 있다는 것을 깨달음.