app.yaml은 앱의 런타임, 환경 변수, 리소스를 정의하는 핵심 설정 파일입니다.
app.yaml이 하는 역할
app.yaml은 Databricks Apps에서 앱의 실행 방법을 선언적으로 정의하는 설정 파일 입니다. Kubernetes에 익숙하다면 Deployment YAML과 유사한 역할이라고 이해하면 됩니다. Databricks가 이 파일을 읽어서 “어떤 명령으로 앱을 시작할지”, “어떤 환경변수를 주입할지”, “어떤 Databricks 리소스에 접근할지”를 결정합니다.
코드가 “무엇을 하는가”를 정의한다면, app.yaml은 “어떻게 실행되는가” 를 정의합니다. 잘 작성된 코드도 app.yaml이 잘못되면 배포가 실패하거나 앱이 비정상적으로 동작합니다. 반대로 app.yaml을 정확하게 구성하면 코드 수정 없이 다른 환경(dev/staging/prod)으로 앱을 이동할 수 있습니다.
참고 핵심 원칙:app.yaml에는 “무엇이 바뀔 수 있는가”를 넣고, 코드에는 “어떻게 동작하는가”를 넣습니다. Warehouse ID, Endpoint 이름, Secret 값 등은 환경에 따라 바뀌므로app.yaml에서 관리하고, 코드에서는os.getenv()로 참조합니다. 이 분리 원칙을 지키면 하나의 코드베이스로 여러 환경에 배포할 수 있습니다.
전체 구조
아래는app.yaml의 전체 구조를 보여주는 예시입니다. 각 섹션의 역할을 주석으로 표시했습니다.
command(앱을 어떻게 시작할지), env(실행 시 어떤 환경변수를 주입할지), resources(어떤 Databricks 리소스에 접근할지). 각 섹션은 독립적이며, 필요한 섹션만 사용해도 됩니다. 예를 들어 환경변수나 리소스가 필요 없는 간단한 앱이라면 command만 있어도 됩니다.
command 필드
커스텀 실행 커맨드를 배열로 지정합니다. 셸 환경에서 실행되지 않으므로 파이프(|)나 리다이렉트(>)는 사용할 수 없습니다.
참고 왜 배열 형태인가?command가 문자열이 아닌 배열인 이유는 셸 해석 없이 직접 실행 되기 때문입니다. Docker의ENTRYPOINT나 Kubernetes의command와 동일한 방식입니다. 배열의 첫 번째 요소가 실행 파일이 되고, 나머지가 인수가 됩니다. 셸을 거치지 않으므로 환경변수 확장($VAR)을 제외한 셸 기능(파이프, 리다이렉트, glob 등)은 사용할 수 없습니다.$DATABRICKS_APP_PORT같은 환경변수만 예외적으로 치환됩니다.
프레임워크별 커맨드 예시
아래 각 프레임워크별로 올바른command 설정과, 왜 그렇게 설정하는지 를 설명합니다.
Streamlit:
['streamlit', 'run', 'app.py', '--server.port', '${DATABRICKS_APP_PORT}']를 사용하세요.
Flask (Gunicorn):
app:app은 “app.py 파일의 app 객체”를 의미합니다. -w 4는 워커 프로세스 수인데, Medium 사이즈(2 vCPU) 기준으로 CPU 코어 수의 2배인 4가 적절합니다. Gunicorn은 기본적으로 0.0.0.0:8000에 바인딩하므로 별도 호스트/포트 설정이 필요 없는 경우가 많습니다.
FastAPI (Uvicorn):
--host 0.0.0.0은 모든 네트워크 인터페이스에서 접속을 허용 한다는 의미입니다. 이것이 127.0.0.1(localhost)가 아닌 이유는, 컨테이너 내부에서 실행되는 앱이 외부(리버스 프록시)로부터 요청을 받아야 하기 때문입니다. 127.0.0.1로 바인딩하면 컨테이너 외부에서 접근할 수 없어 앱이 시작되지만 URL 접속이 불가능합니다. --port에 ${DATABRICKS_APP_PORT}를 사용하는 이유는, Databricks가 동적으로 할당한 포트에서 앱이 리슨해야 리버스 프록시가 트래픽을 전달할 수 있기 때문입니다.
Gradio:
app.py 내에서 demo.launch(server_name="0.0.0.0", server_port=int(os.environ.get("DATABRICKS_APP_PORT", 7860)))으로 서버 설정을 코드에서 직접 합니다. 따라서 command는 단순히 Python 스크립트를 실행하기만 하면 됩니다.
Dash:
app.run(host="0.0.0.0", port=int(os.environ.get("DATABRICKS_APP_PORT", 8050)))으로 서버를 설정합니다.
참고
DATABRICKS_APP_PORT 환경 변수는 런타임에 실제 포트 번호로 자동 치환됩니다.
주의 command 관련 흔한 실수:
- 셸 문법 사용:
command: 'streamlit run app.py && echo done'— 파이프나&&는 사용할 수 없습니다. - 문자열로 지정:
command: 'uvicorn app:app'— 반드시 배열 형태여야 합니다. - 포트 미지정: FastAPI/Flask에서 포트를 지정하지 않으면 기본 포트와 Databricks 포트가 불일치하여 접속 불가.
- host 미설정:
0.0.0.0대신127.0.0.1로 바인딩하면 컨테이너 외부에서 접근 불가.
env 필드
환경 변수를 정의합니다. 두 가지 방식이 있으며, 보안 수준에 따라 반드시 올바른 방식을 선택 해야 합니다.value와 valueFrom의 차이가 중요합니다.
| 방식 | 동작 | 보안 수준 | 사용 대상 |
|---|---|---|---|
value | 값이 app.yaml에 평문으로 저장됨 | 낮음— Git에 커밋되면 노출됨 | 기능 플래그, 로그 레벨, 타임존 등 비민감 설정 |
valueFrom | 리소스 이름만 참조, 실제 값은 런타임에 Databricks가 주입 | 높음— 코드나 설정 파일에 값이 노출되지 않음 | Warehouse ID, Endpoint 이름, API 키, 비밀번호 |
valueFrom을 사용하면 app.yaml에는 리소스의 이름 만 기록되고, 실제 값(Warehouse ID, Secret 값 등)은 Databricks가 앱 실행 시 자동으로 주입합니다. 이는 Kubernetes의 secretKeyRef와 동일한 패턴입니다.
주의 시크릿이나 민감 정보는 절대value에 직접 넣지 마세요. 반드시valueFrom을 사용하여 리소스로 관리하세요.
참고 실무 팁:value에 넣어도 되는지 판단하는 기준은 간단합니다. “이 값이 GitHub 퍼블릭 리포지토리에 올라가도 문제없는가?”를 생각해 보세요.LOG_LEVEL=debug는 노출되어도 문제가 없지만, Warehouse ID는 워크스페이스 구조를 노출하므로valueFrom을 사용하는 것이 바람직합니다.
resources 필드
resources 섹션은 앱이 사용할 Databricks 리소스를 선언합니다. 여기서 선언한 리소스는 env 섹션에서 valueFrom으로 참조할 수 있습니다.
name은 app.yaml 내에서 고유해야 하며, env 섹션의 valueFrom에서 이 이름으로 참조합니다. type은 Databricks가 지원하는 리소스 유형을 지정합니다. 리소스의 실제 값(Warehouse ID, Endpoint 이름 등)은 UI의 Configure 화면에서 설정하거나, 배포 시 지정합니다.
참고 resources와 env의 관계:resources는 “이 앱이 어떤 리소스를 사용하는지” 선언하고,env의valueFrom은 “그 리소스의 값을 어떤 환경변수로 주입할지” 지정합니다. 이 2단계 분리 덕분에 리소스 값이 바뀌어도 코드를 수정할 필요가 없습니다.
프레임워크별 완전한 app.yaml 예시
아래는 각 프레임워크별로 실제 프로덕션에서 사용할 수 있는 완전한app.yaml 예시입니다. 각 설정의 이유를 함께 설명합니다.
Streamlit (데이터 대시보드)
--server.headless true는 Streamlit이 브라우저를 자동으로 열지 않도록 합니다 (서버 환경에서는 브라우저가 없으므로 필수). STREAMLIT_GATHER_USAGE_STATS=false는 Streamlit의 사용 통계 수집을 비활성화합니다.
FastAPI (REST API)
--workers 2는 Medium 사이즈(2 vCPU) 기준으로 적절한 워커 수입니다. 워커 수를 CPU 코어 수보다 많이 설정하면 오히려 컨텍스트 스위칭 오버헤드로 성능이 저하됩니다.
Gradio (AI 챗봇)
serving-endpoint 리소스가 필요합니다. 대화 이력을 저장하려면 sql-warehouse도 함께 설정합니다.
Dash (인터랙티브 대시보드)
DASH_DEBUG=false는 프로덕션에서 디버그 모드를 비활성화합니다. 디버그 모드가 켜져 있으면 에러 발생 시 스택 트레이스가 사용자에게 노출될 수 있어 보안상 위험합니다.
컴퓨트 사이즈
앱의 CPU 및 메모리를 선택할 수 있습니다. 아래 표는 각 사이즈의 사양과 비용, 적합한 사용 사례를 보여줍니다.| 사이즈 | CPU | 메모리 | 비용 | 용도 |
|---|---|---|---|---|
| Medium(기본값) | 최대 2 vCPU | 6 GB | 0.5 DBU/시간 | 대시보드, 간단한 시각화, 폼 |
| Large | 최대 4 vCPU | 12 GB | 1 DBU/시간 | 대용량 데이터 처리, 고동시성, 연산 집약적 작업 |
팁 대부분의 앱은 Medium으로 충분합니다. 성능 이슈가 발생하거나 리소스 요구가 높은 경우에만 Large로 업그레이드하세요.
참고 사이즈 선택 가이드: Streamlit 대시보드나 Gradio 챗봇처럼 사용자 요청을 받아 SQL Warehouse나 Model Serving에 전달하는 “프록시” 역할의 앱은 자체적으로 연산을 거의 하지 않으므로 Medium 으로 충분합니다. 앱 내부에서 Pandas로 대용량 DataFrame을 처리하거나, 이미지/비디오 변환 등 CPU 집약적 작업을 수행하는 경우에만 Large 가 필요합니다. 확신이 없다면 Medium으로 시작하고, 앱 로그에서 OOM(Out of Memory) 에러가 발생하면 Large로 전환하세요.
흔한 실수 5가지와 해결 방법
app.yaml 작성 시 가장 빈번하게 발생하는 실수와 해결 방법을 정리합니다.
| # | 실수 | 증상 | 해결 방법 |
|---|---|---|---|
| 1 | 포트 불일치 | 앱이 Running이지만 URL 접속 시 502 에러 | command에서 --port ${DATABRICKS_APP_PORT} 사용. Streamlit은 --server.port, FastAPI는 --port, Flask/Gunicorn은 -b 0.0.0.0:${DATABRICKS_APP_PORT} |
| 2 | command를 문자열로 지정 | 배포 실패 또는 앱 시작 실패 | command: 'streamlit run app.py'가 아닌 command: ['streamlit', 'run', 'app.py'] 배열 형태 사용 |
| 3 | 리소스 이름 오타 | valueFrom에서 참조하는 이름이 resources에 없어 환경변수가 빈 값 | resources의 name과 env의 valueFrom이 정확히 일치하는지 확인 |
| 4 | requirements.txt 누락 | ModuleNotFoundError로 앱 Crash | 로컬에서 pip freeze로 확인하고, 사용하는 모든 패키지를 requirements.txt에 포함 |
| 5 | Python 버전 충돌 | 특정 패키지가 설치 실패 | Databricks Apps는 Python 3.10+를 사용합니다. Python 3.8 전용 패키지가 있다면 호환되는 버전을 명시 |
주의 가장 디버깅이 어려운 실수: 리소스 이름 오타(#3)입니다. 이 경우 배포는 성공하고 앱도Running이 되지만, 환경변수가 빈 값이 되어 런타임에 예상치 못한 오류가 발생합니다.app.yaml에서resources의name과env의valueFrom값을 복사-붙여넣기로 동일하게 맞추는 것이 안전합니다.