axhub.yaml
ax-hub-backend deploy manifest domain 기준 canonical manifest schema
axhub.yaml은 저장소에 커밋하는 배포 계약 파일이에요. 템플릿으로 만든 앱은 이 파일이 이미 맞춰져 있어서 보통 손댈 일이 없어요. 빌드 명령을 직접 고정하거나, 앱 전용 데이터베이스·스토리지를 선언하거나, compose 진입 서비스를 지정할 때 이 페이지에서 정확한 필드를 찾아보세요. 파일이 없어도 배포는 돼요 — AxHub가 저장소를 보고 빌드 방법을 알아서 정하고, 그 순서도 이 페이지에 있어요. 각 기능을 실제로 붙이는 절차는 SDK 설치하기부터 이어지는 기능 가이드에 있어요.
최소 예시
version: axhub/v1
name: hello-axhub
root: web # 앱이 하위 폴더에 있을 때만 (monorepo)
runtime:
port: 3000
health_path: /
build:
strategy: auto
framework: node
deploy_method: docker
dockerfile: Dockerfile
database:
engine: postgres
storage:
enabled: true
env:
required:
- name: DATABASE_URL
scope: runtime
optional:
- name: NEXT_PUBLIC_API_URL
scope: build
ci:
commands:
- npm test
timeout: 300모든 field는 optional이에요. 비어 있는 값은 DB 설정 또는 resolver 기본값으로 떨어져요.
root — 앱이 하위 폴더에 있을 때
한 저장소 안에 여러 프로젝트가 있고 실제 배포 대상이 하위 폴더(예: web/)라면, 저장소 최상위 axhub.yaml에 한 줄을 적어요.
root: web이 한 줄이면 그 폴더가 앱의 최상위인 것처럼 동작해요. 우회용 Dockerfile을 최상위에 두고 버티던 걸 걷어내고, 자동 감지와 Dockerfile 자동 생성을 그대로 받을 수 있어요.
무엇이 root 기준이 되나요
- Dockerfile · compose 파일 · 프레임워크 마커(
package.json등) 감지 - Dockerfile 자동 생성과 빌드 컨텍스트(복사 범위 ·
.dockerignore) - 이 파일의 다른 경로 필드 —
build.dockerfile·build.compose_file·build.static_output_dir·metadata.icon - CI 명령과 정적 사이트 빌드가 실행되는 위치
즉 root: web + dockerfile: docker/prod.Dockerfile이면 실제로는 web/docker/prod.Dockerfile을 봐요. "이건 저장소 기준, 저건 앱 기준"을 외울 필요가 없어요.
규칙
| 쓴 값 | 결과 |
|---|---|
web · ./web · web/ | 전부 web으로 같게 봐요 |
web/app | 두 단계 이상 깊어도 돼요 |
. · ./ | 미선언과 같아요 (저장소 최상위) |
/web · ../other | 배포 전에 실패해요 — 저장소 안의 상대 경로만 돼요 |
| 그 폴더에 파일이 하나도 없음 | 배포 전에 실패해요 |
설정은 저장소 최상위 axhub.yaml에서만 읽어요. web/axhub.yaml이 같이 있으면 무시하고 배포 결과에 경고 한 줄을 남겨요. 반대로 최상위에 파일이 없고 web/axhub.yaml만 있으면 root 선언이 없는 것으로 봐요.
root를 선언하면 저장소 최상위의 Dockerfile은 무시돼요 — root가 곧 앱의 경계예요- 콘솔이 아니라 저장소 설정이라, push 한 번으로 적용돼요 (재연결 · 콘솔 조작 없음)
- 보안 스캔은 그대로 저장소 전체를 봐요 (범위를 좁히지 않아요)
root를 선언하지 않은 앱은 이 기능이 생기기 전과 완전히 똑같이 동작해요
파일과 우선순위
| 항목 | 현재 계약 |
|---|---|
| canonical filename | axhub.yaml |
| max size | 8 KiB |
| priority | manifest > DB AppSpecData > resolver default |
| deploy resolution | manifest → Dockerfile → Compose → Railpack auto-detection |
빌드 방법이 정해지는 순서
AxHub는 저장소 루트에 어떤 파일이 있는지를 보고 빌드 방법을 정해요. 아래 순서대로 위에서부터 검사하고, 하나라도 맞으면 거기서 멈춰요.
각 단계에서 정확히 무엇을 보는지는 이래요.
axhub.yaml— 있으면 이 manifest를 우선 적용해요.build.dockerfile지정,strategy: pinned+start고정 실행이 이 단계에서 반영돼요.Dockerfile— 루트에 있으면 단일 이미지로 빌드해요.- Compose 파일 —
docker-compose.yml·docker-compose.yaml·compose.yaml·compose.yml순서로 찾아요. - Railpack 자동 감지 — 위가 전부 없으면 마커 파일로 프레임워크를 추론해요:
package.json→node ·go.mod→go ·requirements.txt/pyproject.toml→python ·Gemfile→ruby ·pom.xml/Gradle 파일→java·kotlin ·Cargo.toml→rust.
넷 다 없으면 빌드할 방법을 찾지 못해 배포가 실패해요. 빌드·실행 명령이 애매하거나 짐작에 맡기지 않고 직접 고정하려면 axhub.yaml을 저장소에 두면 돼요 — 최소 예시에서 시작하세요.
최상위 필드
| 필드 | 타입 | 설명 |
|---|---|---|
version | string | 현재 문서화 값은 axhub/v1. unknown 값은 forward-compatible하게 받아들일 수 있지만 known fields 검증은 유지돼요. |
name | string | 표시용 label. 저장된 앱 이름과 강제 동기화하지 않아요. |
root | string | 앱이 하위 폴더에 있을 때 그 폴더를 앱의 최상위로 삼아요 (monorepo). |
metadata | object | 앱 이름 · 설명 · 아이콘을 저장소에 두고 코드와 함께 옮겨요. |
runtime | object | port · health probe path · compose 진입 서비스(entry_service). |
build | object | build/deploy method와 command hints. |
database | object | 앱 전용 DB 요청 — engine: postgres. |
storage | object | 앱 전용 파일 스토리지 요청 — enabled: true. |
env | object | 필요한 env 이름과 scope. 값은 절대 manifest에 저장하지 않아요. |
ci | object | build 전 CI command와 timeout. |
runtime
| 필드 | 타입 | 제한 |
|---|---|---|
port | integer | 1~65535. 비어 있으면 Dockerfile EXPOSE 또는 adapter 기본값. compose 모드에서는 무시돼요 — 포트는 compose 파일이 결정해요. |
entry_service | string | compose 전용. 바깥 주소에 연결할 서비스 이름. docker · static 모드에서는 무시돼요. 자세한 규칙은 아래 compose 진입 서비스를 보세요. |
health_path | string | liveness/readiness probe path. 비어 있으면 / fallback. |
replicas | map | compose 전용. 서비스 이름 → 파드 수. 값은 1 이상이고, 안 적은 서비스는 플랫폼 기본값을 따라요. |
레플리카를 compose 파일의 deploy.replicas가 아니라 여기에 두는 이유가 있어요. compose 파일은 로컬에서 docker compose up으로 그대로 돌릴 수 있게 순수하게 두고, "어떻게 배포할지"는 매니페스트가 갖는 거예요.
runtime:
replicas:
web: 2
beat: 1 # 크론은 하나만 — 두 개면 매번 두 번 발화해요
redis: 1스케줄러와 공유 저장소는 1로 못 박으세요. 서비스마다 지정하지 않으면 전부 같은 수로 뜨는데, 크론 서비스가 2개가 되면 작업이 두 번 실행돼요.
build
| 필드 | 값 | 설명 |
|---|---|---|
strategy | `auto | pinned` |
framework | `node | python |
install | string | framework preset용 install command. |
build | string | framework preset용 build command. |
start | string | runtime start command. |
dockerfile | path | 기본 Dockerfile 대신 사용할 Dockerfile path. |
deploy_method | `docker | compose |
compose_file | path | compose mode에서 사용할 compose file. 기본 docker-compose.yml. |
static_output_dir | path | static mode에서 업로드할 빌드 산출물 디렉터리. 기본 dist. |
deploy_method는 앱 DB에서도 immutable이에요. manifest 값이 앱의 DB 값과 다르면 배포가 실패하니, 바꾸려면 보통 새 앱으로 재생성해야 해요.
compose 진입 서비스
여러 서비스로 이루어진 compose 앱에서 바깥 주소({앱}.{테넌트}.…)에 연결되는 서비스는 하나뿐이에요. 명시하지 않으면 compose 파일에서 호스트 포트를 명시한 첫 번째 서비스가 진입점이 돼요.
순서에 기대지 않으려면 axhub.yaml에 직접 적어요(권장):
runtime:
entry_service: backend # compose 전용 — 이 서비스가 바깥 주소에 연결돼요- 선언이 있으면 호스트 포트를 안 적은 서비스도 진입점이 될 수 있어요 —
ports: [{target: 8080}]이든expose: ["8080"]이든 그 서비스가 열리고, 포트는 선언된 것 중 첫 번째를 써요. - 선언한 이름이 compose 파일에 없으면 배포가 실패해요 (
build.entry_service_not_found— 실패 메시지에 실제 서비스 목록이 함께 나와요). - 선언한 서비스에 포트가 하나도 없으면 역시 실패해요 (
build.entry_service_has_no_port). - 미선언이면 기존 순서 규칙이 그대로 적용돼요.
포트 작성법별 실제 동작
"진입 후보"가 아니어도 내부 통신 포트는 살아 있어요 — 둘은 별개의 축이에요. 서비스끼리는 서비스 이름으로 서로를 불러요(http://backend:8080).
| 작성법 | 진입 후보 | 내부 통신 포트 |
|---|---|---|
ports: ["3000:3000"] | ✅ 3000 | 3000 |
ports: [{target: 7000, published: "7000"}] | ✅ 7000 | 7000 |
ports: ["4000"] (호스트 포트 없는 short syntax) | ❌ | 4000 |
ports: [{target: 6000}] (published 없음) | ❌ | 6000 |
expose: ["5000"] | ❌ | 5000 |
| 포트 선언 없음 | ❌ | ❌ 없음 |
포트를 하나도 안 적은 서비스는 형제 서비스가 이름으로 부를 수 없어요. 로컬 docker compose는 선언 없이도 서로 도달하니 로컬에서만 멀쩡한 상태가 되기 쉬워요 — 이 경우 배포 결과에 경고가 남아요. 큐 워커처럼 서버가 아니면 무시해도 돼요.
expose:는 내부 전용 포트로 흡수돼요 — 내부 통신은 열리고 진입점은 되지 않아요. 범위 표기(3000-3005) · udp는 지원하지 않고 경고와 함께 무시돼요.
진입점을 바꾸려면 호스트 포트 명시를 옮기거나 entry_service 선언을 바꾸고 다시 배포하면 한 번에 반영돼요. 주소 · 인증서 · 도메인 설정은 앱 단위라 그대로 유지돼요.
배포 결과에 뜨는 경고
경고는 배포를 막지 않아요 — 의도한 구성이면 무시해도 돼요.
| 경고 | 뜻 | 의도한 것이면 |
|---|---|---|
| 포트를 연 서비스가 여러 개 | 그중 하나만 바깥에 연결돼요 | 나머지를 target: · expose:로 바꿔 의도를 분명히 |
| 서비스를 이름으로 부를 수 없어요 | 포트를 하나도 안 적었어요 | 워커 · 배치면 무시해도 돼요 |
| 이 앱은 주소로 열리지 않아요 | 호스트 포트를 연 서비스가 없어요 | 배치 · 워커 전용 앱이면 정상이에요 |
| expose 항목을 읽을 수 없어요 | 범위 · udp는 미지원이에요 | 포트 번호 하나씩 나눠 적어요 |
| 선언에 따라 서비스가 연결돼요 | entry_service가 이겼고, 포트를 연 다른 서비스가 있어요 | 의도한 것이면 무시 |
metadata
앱 이름 · 설명 · 아이콘을 저장소에 적어 두면 코드와 함께 옮겨 다녀요. 콘솔에서 따로 채우지 않아도 돼요.
metadata:
name: 사내 위키
description: 팀 문서를 한곳에서 찾는 앱
icon: assets/icon.png
icon_dark: assets/icon-dark.png| 필드 | 설명 |
|---|---|
name | 앱의 실제 이름. 최상위 name(표시용 label)과 다른 자리예요 |
description | 스토어 · 목록에 나오는 설명 |
icon · icon_dark | 저장소 안의 이미지 경로. root를 선언했으면 그 폴더 기준이에요 |
- 전부 선택 항목이고, 운영에 실제로 반영될 때 적용돼요 — 운영에 바로 배포하거나, 스테이징 빌드를 운영에 반영(promote)할 때예요
- 값을 비워 두는 것은 "건드리지 말라"는 뜻이에요. 빈 문자열로 기존 값을 지울 수는 없어요
database
앱 전용 Postgres DB를 manifest로 요청해요. 선언한 채 배포하면 앱 전용 DB가 만들어지고, 런타임에 DATABASE_URL(쿼리용) · DIRECT_DATABASE_URL(마이그레이션용)이 자동 주입돼요.
database:
engine: postgres| 필드 | 값 | 설명 |
|---|---|---|
engine | postgres | 현재 지원하는 유일한 엔진. CLI apps raw-db enable과 같은 효과예요. |
- production과 staging은 서로 다른 DB를 받아요 — 각 환경에 배포할 때 그 환경의 DB가 만들어져요.
- 선언을 지워도 DB와 주입은 유지돼요. 끄기는 콘솔 · CLI(
apps raw-db disable)로만 해요. - 정적 호스팅 앱(
deploy_method: static)이 선언하면 배포가prepare.database_unsupported_for_static으로 실패해요. - 사용법은 데이터베이스 연결하기를 보세요.
storage
앱 전용 S3 호환 파일 스토리지를 manifest로 요청해요. 선언한 채 배포하면 앱 전용 버킷이 만들어지고, 런타임에 STORAGE_ENDPOINT · STORAGE_BUCKET · STORAGE_ACCESS_KEY · STORAGE_SECRET_KEY가 자동 주입돼요.
storage:
enabled: true| 필드 | 값 | 설명 |
|---|---|---|
enabled | boolean | true면 다음 배포에서 프로비저닝. 유일한 필드예요 — 다른 키는 배포 전에 거부돼요. |
- 버킷은 앱당 1개이고 production · staging이 공유해요.
- 선언을 지워도 버킷 · 파일 · 주입은 유지돼요. 앱 영구 삭제 시에만 함께 삭제돼요.
- 정적 호스팅 앱이 선언하면 배포가
prepare.storage_unsupported_for_static으로 실패해요. - 사용법 · 콘솔 API · 제한은 스토리지 연결하기를 보세요.
env
Manifest에는 env 이름과 scope만 적어요. 값은 axhub env set/update로 저장해요.
env:
required:
- name: DATABASE_URL
scope: runtime
- name: NEXT_PUBLIC_API_URL
scope: build
optional:
- name: SENTRY_DSN
scope: both| scope | 주입 위치 |
|---|---|
build | build arg로만 전달 |
runtime | pod env로만 전달 |
both | build와 runtime 양쪽에 필요 |
scope를 생략하면 기본값은 runtime이에요. 이름은 비어 있거나 줄바꿈/= 문자를 포함할 수 없어요.
ci
| 필드 | 제한 |
|---|---|
commands | 최대 10개 |
timeout | 1~600초 (검증 범위) |
CI command는 build 전에 실행되고, 실패하면 deploy가 멈춰요.
timeout 값은 현재 검증만 되고 실행 예산에는 반영되지 않아요 — 파이프라인의 CI 단계 예산은 10분으로 고정돼 있어요.