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: OPENAI_API_KEY
      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.
    • 자동으로 만든 Dockerfile 은 Node · Python · Go · Ruby · Java 밖의 언어(Rust 등)에선 툴체인이 없는 이미지로 만들어져 빌드가 실패할 수 있어요. 이런 앱은 Dockerfile 을 직접 둬요.

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

최상위 필드

필드타입설명
versionstring현재 문서화 값은 axhub/v1. unknown 값은 forward-compatible하게 받아들일 수 있지만 known fields 검증은 유지돼요.
namestring표시용 label. 저장된 앱 이름과 강제 동기화하지 않아요.
rootstring앱이 하위 폴더에 있을 때 그 폴더를 앱의 최상위로 삼아요 (monorepo).
metadataobject앱 이름 · 설명 · 아이콘을 저장소에 두고 코드와 함께 옮겨요.
runtimeobjectport · health probe path · 헬스체크 대기 시간(healthcheck) · compose 진입 서비스(entry_service).
buildobjectbuild/deploy method와 command hints.
databaseobject앱 전용 DB 요청 — engine: postgres. 필요하면 extensions로 Postgres 확장, roles로 추가 DB 계정까지.
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.
healthcheckobjectdocker 전용. 앱이 처음 뜰 때까지 기다리는 헬스체크 대기 시간을 정해요. compose · static 모드에서는 무시돼요. 자세한 규칙은 아래 헬스체크 대기 시간을 보세요.
replicasmapcompose 전용. 서비스 이름 → 파드 수. 값은 1 이상이고, 안 적은 서비스는 플랫폼 기본값을 따라요.
cacheobjectdocker 전용. 재시작·재배포 뒤에도 남는 캐시 볼륨. size(1Gi50Gi)와 paths(절대경로 18개). compose 는 named volume 을 쓰세요. 자세한 규칙은 아래 캐시 볼륨을 보세요.
ephemeral_storagestring컨테이너 임시 스토리지 상한. 2Gi·1536Mi 처럼 Mi/Gi 단위, 1Gi 이상 10Gi 이하. 비어 있으면 플랫폼 기본 1Gi. compose 모드는 모든 서비스에 같은 값, static 모드는 무시돼요. 자세한 규칙은 아래 임시 스토리지를 보세요.

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

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

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

캐시 볼륨 (cache)

컨테이너 파일시스템은 파드가 죽으면 사라져요. 기동할 때 모델·패키지를 받는 앱은 재시작·재배포마다 같은 파일을 다시 받게 되는데, 그 경로를 캐시 볼륨에 두면 한 번 받은 파일이 남아요.

runtime:
  cache:
    size: 5Gi                          # 1Gi ~ 50Gi
    paths:
      - /root/.cache/huggingface
      - /root/.cache/pip

플랫폼이 운영·스테이징 환경마다 디스크 하나를 만들고 적어 둔 경로들을 거기에 연결해요. 이미지가 non-root 사용자(USER app 등)로 돌아도 그 경로에 쓸 수 있게 열어 둬요. 경로는 서로 겹치면 안 돼요(/a 와 /a/b 동시 지정 불가). size 는 2Gi·1536Mi 처럼 Mi/Gi 단위의 정수로 적어요. 디스크는 그 환경에 cache 가 처음 적용될 때 만들어지고 크기도 그때 정해져요 — 나중에 size 를 바꿔도 기존 디스크가 그대로 쓰여요. 대개는 그 환경에서 cache 를 처음 선언한 배포가 그때예요. 스테이징은 켜는 순간 운영 중인 버전으로 채워지므로, 그 버전이 cache 를 쓰고 있으면 스테이징 디스크는 켤 때 그 버전의 size 로 만들어져요. 그 뒤 스테이징 배포에서 size 를 바꿔도 이 디스크가 그대로 쓰여요.

파드가 하나인 앱에서만 쓸 수 있어요. 디스크 하나에 파드 하나만 붙기 때문에 레플리카가 2 이상이면 배포가 거절돼요. 앱을 일시정지하면 디스크도 지워져요(스테이징이 꺼지면 스테이징 디스크가 지워져요). 지워진 디스크는 그 환경에 cache 가 다시 적용될 때(다음 배포·재개, 스테이징은 다시 켤 때) 새로 만들어지고 크기도 그때 다시 정해져요 — 캐시는 "다시 받으면 되는" 파일에만 쓰세요. compose 앱은 compose 파일의 named volume 이 같은 역할이에요.

저장소를 고칠 수 없으면 같은 값을 앱 설정으로도 둘 수 있어요 — PUT /api/v1/apps/{appID}/runtime-overrides 에 cache·ephemeral_storage·port·health_path·healthcheck 를 보내면 다음 배포·재개부터 적용돼요. 같은 주소를 GET 하면 마지막 운영 배포가 실제로 쓴 값과 그중 앱 설정에서 온 항목(override_fields)을 알려 줘요. 앱 설정은 저장소가 정하지 않은 자리만 채워요 — axhub.yaml 에 선언이 있으면 yaml 이 우선이고, 포트는 Dockerfile 의 숫자 EXPOSE 가 있으면 그쪽이 우선해요.

임시 스토리지 (ephemeral_storage)

컨테이너가 자기 파일시스템(/tmp, 작업 폴더 등)에 쓸 수 있는 크기예요. 기본은 1Gi이고, 넘기면 플랫폼이 파드를 축출(Evicted) 하고 새로 띄워요. 기동할 때 모델·패키지·데이터를 수 GB 받아 두는 앱은 이 한도에 걸려 "받다가 축출되고 다시 받는" 루프를 돌아요. 그런 앱만 올리세요.

runtime:
  ephemeral_storage: 4Gi    # 1Gi ~ 10Gi, Mi/Gi 단위

임시 스토리지는 파드가 죽으면 사라져요. 재시작 뒤에도 남아야 하는 파일(모델 캐시 등)은 이미지에 굽거나 storage(앱 전용 파일 스토리지)에 두세요.

헬스체크 대기 시간 (healthcheck)

배포하면 AxHub는 새 컨테이너가 처음 헬스체크에 응답할 때까지 일정 시간을 기다려요. 이 시간이 헬스체크 대기 시간이에요. 이 시간 안에 응답하지 못하면 컨테이너를 재시작해요. 재시작 뒤에도 뜨지 못하면 배포가 실패하는데, 재시작이 파드 이벤트로 확인되면 healthcheck.startup_budget_exceeded, 아니면 healthcheck.container_crash로 실패해요. 재시작 뒤에 응답하면 경고(startup_budget_restart)만 남기고 배포는 성공할 수 있어요.

헬스체크 대기 시간 = start_period + interval × retries      (60초 ~ 5분)

아무것도 적지 않으면 60초예요. 마이그레이션 · 시드 · 워밍업 때문에 60초 안에 못 뜨는 앱은 이렇게 적어요. 뜻은 Dockerfile의 HEALTHCHECK, compose의 healthcheck와 같아요.

runtime:
  port: 3000
  health_path: /health        # 검사가 치는 경로
  healthcheck:
    start_period: 90s
    interval: 10s
    timeout: 5s
    retries: 6                # → 대기 시간 90 + 10×6 = 150초
필드타입기본값설명
start_periodduration0s이 시간 동안은 실패를 세지 않아요
intervalduration3s검사 간격
timeoutduration5s검사 1회의 응답 제한. 5초보다 작게 적어도 5초로 올려요 — 부팅 중에는 CPU가 부족해 검사 자체가 느려요
retriesinteger20연속 실패 허용 횟수

규칙

  • 값은 단위가 붙은 시간(90s, 2m)만 받아요. 90처럼 단위가 없거나 음수면 배포 전에 거절돼요.
  • 계산 결과가 60초보다 짧으면 60초로, 5분보다 길면 5분으로 맞춰요. 상한을 넘겨도 거절하지는 않아요.
  • 적은 값만 바뀌고 나머지는 기본값으로 채워요. start_period: 60s만 적으면 60 + 3×20 = 120초예요.
  • docker 전용이에요. compose 앱은 compose 파일의 healthcheck가 정본이고 이 블록은 무시돼요 (test가 없으면 포트 검사 · 60초, NONE이거나 disable: true면 검사하지 않아요). 정적 앱도 무시돼요.
  • Dockerfile의 HEALTHCHECK 명령은 읽지 않아요 — 같은 값을 여기에 적어요.
  • 준비(readiness) · 살아있음(liveness) 검사는 이 값과 무관해요. 두 검사는 health_path만 써요.

부팅에 마이그레이션 · 워밍업이 있으면 그 시간까지 더해서 적으세요. 스테이징도 운영과 같은 사양으로 돌기 때문에 뜨는 시간이 두 환경에서 비슷해요 — 스테이징에서 잰 시간을 그대로 기준으로 잡아도 돼요.

  • 스테이징 · 운영 · 운영 반영(promote) · 재개(resume) 모두 같은 대기 시간을 써요. 배포 시점의 health_path · healthcheck가 배포 기록에 함께 남아서, 운영 반영이 코드를 다시 읽지 않아도 같은 값을 써요.
  • 대기 시간 초과 · 크래시 등 실패 사유 코드별 조치는 헬스체크 실패 사유 코드에 있어요.

build

필드값설명
strategyauto | pinned기본 auto. pinned + start면 generated Dockerfile path로 고정 실행 가능.
frameworknode | python | go | ruby | java | kotlin | rust | unknown 등CI/image selection hint.
installstringframework preset용 install command.
buildstringframework preset용 build command.
startstringruntime start command.
dockerfilepath기본 Dockerfile 대신 사용할 Dockerfile path.
deploy_methoddocker | compose | staticDocker 단일 이미지, Compose multi-service, 또는 정적 사이트.
compose_filepathcompose mode에서 사용할 compose file. 기본 docker-compose.yml.
static_output_dirpathstatic mode에서 배포할 빌드 산출물 디렉터리 (앱 루트 기준). 기본 dist. .이면 앱 루트 자체를 배포해요 (빌드 없는 HTML 사이트).

플러그인 import로 정적 앱을 올릴 때 내 컴퓨터에서 도는 빌드는 install · build 값을 쓰지 않아요. 잠금 파일로 고른 패키지 매니저로 의존성을 설치하고 package.json의 build 스크립트를 실행해요 (pnpm-lock.yaml→pnpm · bun.lock 또는 bun.lockb→bun · yarn.lock→yarn · 그 밖에는 npm).

작업공간이 Axhub 저장소(self-hosted Git)를 쓰면 정적 앱도 소스를 앱 저장소에 push하고, push로 시작된 배포가 서버에서 빌드해요. 이때는 install · build 값을 쓰고, 비어 있으면 잠금 파일로 의존성을 설치(package-lock.json→npm ci · pnpm-lock.yaml→pnpm · yarn.lock→yarn · 그 밖에 package.json만 있으면 npm install)한 뒤 package.json이 있을 때 npm run build를 실행해요. 그다음 static_output_dir 폴더를 배포해요. .이면 .git · node_modules를 뺀 앱 루트 전체를 배포해요.

deploy_method는 앱 DB에서도 immutable이에요. manifest 값이 앱의 DB 값과 다르면 배포가 실패하니, 바꾸려면 보통 새 앱으로 재생성해야 해요. axhub.yaml이 없어도 같아요 — compose 파일만 있으면 compose, 자동 감지면 docker로 판정되는데, 이 값이 앱 값과 다르면 같은 실패가 나요.

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
  extensions: [pgvector, pg_trgm]   # 선택
  roles: [runtime, auth]            # 선택
  owner_bypass_rls: true            # 선택
필드값설명
enginepostgres현재 지원하는 유일한 엔진. 앱 전용 DB를 켜는 방법은 이 선언 하나예요.
extensions문자열 목록배포 때 앱 DB에 미리 설치할 Postgres 확장. engine과 함께 적어야 하고, 아래 목록의 이름만 받아요.
roles문자열 목록기존 계정 옆에 더 만들 로그인 계정(최대 3개). 계정마다 DATABASE_URL_<이름> · DIRECT_DATABASE_URL_<이름>이 주입돼요. 이름은 영어 소문자로 시작하는 소문자 · 숫자 16자 이내이고 ro · staging은 쓸 수 없어요. engine과 함께 적어요.
owner_bypass_rlstrue · falsetrue면 기존 계정(표 주인)이 RLS를 건너뛰어요(BYPASSRLS). 표에 FORCE ROW LEVEL SECURITY를 걸 때 마이그레이션 · 백업이 막히지 않게 해요. 기본값 false.

선언할 수 있는 확장은 13개예요 — pgvector(vector로 적어도 같아요) · pg_trgm · citext · unaccent · fuzzystrmatch · uuid-ossp · pgcrypto · hstore · ltree · intarray · tablefunc · btree_gin · btree_gist. 대소문자는 구분하지 않고 중복은 하나로 접혀요. 버전은 DB 인스턴스가 제공하는 기본 버전이 설치돼요.

  • production과 staging은 서로 다른 DB를 받아요 — 각 환경에 배포할 때 그 환경의 DB가 만들어져요. 스테이징을 쓰는 앱은 운영에 반영(promote)할 때 운영 DB가 만들어지고, 이때는 심사한 스테이징 버전의 선언을 따라요. 확장도 각 환경의 DB에 따로 설치돼요.
  • 확장은 앱이 새 버전으로 뜨기 전에 설치돼요. 이미 설치돼 있으면 그대로 두고, 설치에 실패하면 배포도 prepare.raw_db_extension_failed로 실패해요 — "배포는 됐는데 확장이 없는" 상태는 생기지 않아요.
  • 선언을 지워도 DB와 주입은 유지돼요. 끄기는 CLI(apps raw-db disable)로만 해요. 확장도 선언에서 지운다고 회수되지 않아요 — 그 확장을 쓰는 표와 데이터가 그대로 남아요.
  • 정적 호스팅 앱(deploy_method: static)이 선언하면 배포가 prepare.database_unsupported_for_static으로 실패해요.
  • extensions에 목록 밖 이름을 적거나 engine 없이 extensions만 적으면 manifest 검증에서 배포가 시작 단계에 거절돼요. 사유에 문제 항목과 가능한 값이 함께 보여요.
  • roles로 만든 계정은 접속만 되고 표 권한은 없어요 — 어떤 표를 볼지는 앱 마이그레이션이 GRANT로 정해요. RLS를 받는 일반 계정이고, 동시에 5개까지 접속해요. 선언에서 빼면 그 계정은 로그인만 막히고 받았던 권한은 남아요. 계정 만들기에 실패하면 배포가 prepare.raw_db_role_failed로 실패해요.
  • owner_bypass_rls를 빼면 다음 배포에서 BYPASSRLS가 바로 회수돼요. 추가 계정에는 이 권한이 붙지 않아요.
  • 사용법은 데이터베이스 연결하기를, 계정 나누기는 DB 계정을 나눠 쓸 때를 보세요.

storage

앱 전용 S3 호환 파일 스토리지를 manifest로 요청해요. 선언한 채 배포하면 앱 전용 버킷이 만들어지고, 런타임에 STORAGE_ENDPOINT · STORAGE_BUCKET · STORAGE_ACCESS_KEY · STORAGE_SECRET_KEY가 자동 주입돼요.

storage:
  enabled: true
필드값설명
enabledbooleantrue면 다음 배포에서 프로비저닝.
cdnbooleantrue면 버킷 파일을 서명 주소 CDN 으로도 내보내고 STORAGE_CDN_BASE_URL · STORAGE_CDN_SIGN_KEY 를 더 넣어 줘요. enabled: true 와 함께 적어야 해요.

이 두 필드 밖의 키는 배포 전에 거부돼요.

  • 버킷은 앱당 1개이고 production · staging이 공유해요.
  • 선언을 지워도 버킷 · 파일 · 주입은 유지돼요. 앱 영구 삭제 시에만 함께 삭제돼요.
  • 정적 호스팅 앱이 선언하면 배포가 prepare.storage_unsupported_for_static으로 실패해요.
  • 사용법 · 콘솔 API · 제한은 스토리지 연결하기를 보세요.

env

Manifest에는 env 이름과 scope만 적어요. 값은 axhub env set/update로 저장해요.

env:
  required:
    - name: OPENAI_API_KEY
      scope: runtime
    - name: NEXT_PUBLIC_API_URL
      scope: build
  optional:
    - name: SENTRY_DSN
      scope: both
scope주입 위치
buildbuild arg로만 전달
runtimepod env로만 전달
bothbuild와 runtime 양쪽에 필요

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

AxHub 가 넣어 주는 이름(DATABASE_URL · DIRECT_DATABASE_URL · STORAGE_* · PORT 등)은 required 에 적지 않아요. 실행 중에 AxHub 가 넣어 주는 값이라 따로 저장하지 않아도 되고, 빌드에는 들어가지 않아요 — 빌드에 필요하면 scope: build 로 직접 저장해요.

ci

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

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

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