K

axhub.yaml

ax-hub-backend deploy manifest domain 기준 canonical manifest schema

axhub.yaml은 저장소에 커밋하는 배포 계약 파일이에요. 템플릿으로 만든 앱은 이 파일이 이미 맞춰져 있어서 보통 손댈 일이 없어요. 빌드 명령을 직접 고정하거나, 앱 전용 데이터베이스·스토리지를 선언하거나, compose 진입 서비스를 지정할 때 이 페이지에서 정확한 필드를 찾아보세요. 파일이 없어도 배포는 돼요 — AxHub가 저장소를 보고 빌드 방법을 알아서 정하고, 그 순서도 이 페이지에 있어요. 각 기능을 실제로 붙이는 절차는 SDK 설치하기부터 이어지는 기능 가이드에 있어요.

최소 예시

axhub.yaml
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에 한 줄을 적어요.

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 filenameaxhub.yaml
max size8 KiB
prioritymanifest > DB AppSpecData > resolver default
deploy resolutionmanifest → Dockerfile → Compose → Railpack auto-detection

빌드 방법이 정해지는 순서

AxHub는 저장소 루트에 어떤 파일이 있는지를 보고 빌드 방법을 정해요. 아래 순서대로 위에서부터 검사하고, 하나라도 맞으면 거기서 멈춰요.

axhub.yaml있으면 이 설정대로Dockerfile단일 이미지 빌드Compose 파일docker-compose.yml 등자동 감지package.json 등으로 추론위에서부터 검사해서 처음 맞는 방법 하나로 빌드해요
빌드 방법이 정해지는 순서

각 단계에서 정확히 무엇을 보는지는 이래요.

  1. axhub.yaml — 있으면 이 manifest를 우선 적용해요. build.dockerfile 지정, strategy: pinned + start 고정 실행이 이 단계에서 반영돼요.
  2. Dockerfile — 루트에 있으면 단일 이미지로 빌드해요.
  3. Compose 파일docker-compose.yml · docker-compose.yaml · compose.yaml · compose.yml 순서로 찾아요.
  4. Railpack 자동 감지 — 위가 전부 없으면 마커 파일로 프레임워크를 추론해요: package.json→node · go.mod→go · requirements.txt/pyproject.toml→python · Gemfile→ruby · pom.xml/Gradle 파일→java·kotlin · Cargo.toml→rust.

넷 다 없으면 빌드할 방법을 찾지 못해 배포가 실패해요. 빌드·실행 명령이 애매하거나 짐작에 맡기지 않고 직접 고정하려면 axhub.yaml을 저장소에 두면 돼요 — 최소 예시에서 시작하세요.

최상위 필드

필드타입설명
versionstring현재 문서화 값은 axhub/v1. unknown 값은 forward-compatible하게 받아들일 수 있지만 known fields 검증은 유지돼요.
namestring표시용 label. 저장된 앱 이름과 강제 동기화하지 않아요.
rootstring앱이 하위 폴더에 있을 때 그 폴더를 앱의 최상위로 삼아요 (monorepo).
metadataobject앱 이름 · 설명 · 아이콘을 저장소에 두고 코드와 함께 옮겨요.
runtimeobjectport · health probe path · compose 진입 서비스(entry_service).
buildobjectbuild/deploy method와 command hints.
databaseobject앱 전용 DB 요청 — engine: postgres.
storageobject앱 전용 파일 스토리지 요청 — enabled: true.
envobject필요한 env 이름과 scope. 값은 절대 manifest에 저장하지 않아요.
ciobjectbuild 전 CI command와 timeout.

runtime

필드타입제한
portinteger1~65535. 비어 있으면 Dockerfile EXPOSE 또는 adapter 기본값. compose 모드에서는 무시돼요 — 포트는 compose 파일이 결정해요.
entry_servicestringcompose 전용. 바깥 주소에 연결할 서비스 이름. docker · static 모드에서는 무시돼요. 자세한 규칙은 아래 compose 진입 서비스를 보세요.
health_pathstringliveness/readiness probe path. 비어 있으면 / fallback.
replicasmapcompose 전용. 서비스 이름 → 파드 수. 값은 1 이상이고, 안 적은 서비스는 플랫폼 기본값을 따라요.

레플리카를 compose 파일의 deploy.replicas가 아니라 여기에 두는 이유가 있어요. compose 파일은 로컬에서 docker compose up으로 그대로 돌릴 수 있게 순수하게 두고, "어떻게 배포할지"는 매니페스트가 갖는 거예요.

runtime:
  replicas:
    web: 2
    beat: 1      # 크론은 하나만 — 두 개면 매번 두 번 발화해요
    redis: 1

스케줄러와 공유 저장소는 1로 못 박으세요. 서비스마다 지정하지 않으면 전부 같은 수로 뜨는데, 크론 서비스가 2개가 되면 작업이 두 번 실행돼요.

build

필드설명
strategy`autopinned`
framework`nodepython
installstringframework preset용 install command.
buildstringframework preset용 build command.
startstringruntime start command.
dockerfilepath기본 Dockerfile 대신 사용할 Dockerfile path.
deploy_method`dockercompose
compose_filepathcompose mode에서 사용할 compose file. 기본 docker-compose.yml.
static_output_dirpathstatic 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"]✅ 30003000
ports: [{target: 7000, published: "7000"}]✅ 70007000
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
필드설명
enginepostgres현재 지원하는 유일한 엔진. 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
필드설명
enabledbooleantrue면 다음 배포에서 프로비저닝. 유일한 필드예요 — 다른 키는 배포 전에 거부돼요.
  • 버킷은 앱당 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주입 위치
buildbuild arg로만 전달
runtimepod env로만 전달
bothbuild와 runtime 양쪽에 필요

scope를 생략하면 기본값은 runtime이에요. 이름은 비어 있거나 줄바꿈/= 문자를 포함할 수 없어요.

ci

필드제한
commands최대 10개
timeout1~600초 (검증 범위)

CI command는 build 전에 실행되고, 실패하면 deploy가 멈춰요.

timeout 값은 현재 검증만 되고 실행 예산에는 반영되지 않아요 — 파이프라인의 CI 단계 예산은 10분으로 고정돼 있어요.