AXHub에 맞게 앱 만들기
AXHub 서버에서 앱이 실제로 뜨려면 지켜야 하는 조건과, 어기면 나는 배포 실패를 규칙별로 정리했어요. 다른 곳에서 먼저 만든 앱을 올리기 전에 확인하세요.
AXHub에 올린 앱은 내 컴퓨터가 아니라 AXHub 서버에서 돌아요. 그래서 내 컴퓨터에서 잘 되던 앱도 아래 조건을 어기면 배포가 실패하거나, 배포는 됐는데 기능이 동작하지 않아요.
규칙마다 무엇을 보면 되는지 · 왜 안 되는지 · 어떻게 고치는지 · 배포가 실패하면 보이는 코드를 적었어요.
AI 에게 맡길 때 — Claude 같은 AI 가 배포 전에 이 문서를 기준으로 앱을 점검해요. 규칙마다 적힌 처리 방식을 따라요.
- 바로 고침 — 사용자에게 묻지 않고 고치고, 배포 뒤에 "이렇게 맞춰서 올렸어요"로 알려요. 앱이 하는 일은 바뀌지 않아요.
- 물어봄 — 앱이 하는 일이 바뀌거나, 사용자만 아는 값이 필요하거나, 되돌리기 어려운 선택이에요. 한 번만 묻고, 답을 기억해 다음 배포에는 다시 묻지 않아요.
- 알림 — 고칠 수 없거나 고칠 필요가 없는 제약이에요. 한 번 알려 주고 진행해요.
이 문서에 없는 문제도 확실하면 보고해요. 다만 테스트·예시 코드·README 에만 있는 흔적은 문제로 보지 않아요.
1. 앱이 뜨는 조건
아래는 Docker 로 실행되는 앱(대부분의 서버 앱) 기준이에요. 정적 사이트는 6절을 보세요.
AXHub 는 배포 뒤 앱 주소로 상태 확인 요청을 보내요. 60초 안에 정상 응답이 오지 않으면 앱을 다시 시작하고, 결국 배포가 실패해요. 이때 실패 코드는 대부분 healthcheck.startup_budget_exceeded("N초 안에 헬스체크에 응답하지 못했어요 … start_period 를 올려 보세요") 이고, 확인이 안 되면 healthcheck.container_crash 예요. 안내 문구는 대기 시간을 늘리라고 하지만, 실제 원인은 1-1 ~ 1-6 중 하나인 경우가 많아요. 대기 시간을 늘리기 전에 먼저 확인해요.
1-1. 포트는 PORT 환경변수로 연다
- 무엇을 보나 — 서버가
3000,5000,8000같은 숫자를 직접 적어 열고 있는지 (app.listen(3000),uvicorn ... --port 8000). - 왜 안 되나 — AXHub 는 앱이 열 포트를 정해 환경변수
PORT로 넣어 주고, 그 포트로만 상태를 확인해요. 정하는 순서는axhub.yaml의runtime.port→ Dockerfile 의 첫 숫자EXPOSE→ 콘솔 앱 설정의 포트 → (자동 Dockerfile 이면) 시작 명령의--port·${PORT:-N}→8080이에요. 앱이PORT를 무시하고 다른 포트로 열면 확인이 계속 실패해요. - 고치는 법 —
process.env.PORT(Node),os.environ["PORT"](Python)로 포트를 읽어요. 포트를 꼭 고정해야 하면axhub.yaml에runtime.port로 그 숫자를 적어요. - 처리 — 바로 고침
- 실패 코드 —
healthcheck.startup_budget_exceeded,healthcheck.container_crash
1-2. 모든 주소(0.0.0.0)에서 연다
- 무엇을 보나 — 서버가
localhost나127.0.0.1에서만 열려 있는지 (app.run(host="127.0.0.1"),next start -H localhost). - 왜 안 되나 — 상태 확인 요청은 서버 밖(파드 주소)에서 와서,
127.0.0.1에만 열린 서버에 닿지 않아요. - 고치는 법 —
0.0.0.0으로 열어요. Next.js 는HOSTNAME=0.0.0.0, Flask·FastAPI 는host="0.0.0.0". - 처리 — 바로 고침
- 실패 코드 —
healthcheck.startup_budget_exceeded,healthcheck.container_crash
1-3. 상태 확인 주소는 로그인 없이 빨리 응답한다
- 무엇을 보나 — 상태 확인 주소(기본
/)가 401·403·404·500 을 주는지, 응답 전에 DB·외부 API 를 부르는지. - 왜 안 되나 — 상태 확인은 로그인 정보도
X-AxHub-*헤더도 없이 HTTP GET 으로 와요. 200~399 응답이 켜지는 동안은 5초, 켜진 뒤에는 3초 안에 와야 해요. 로그인 화면으로 한 번 넘기는 302 는 통과해요 (넘어간 화면이 200 이면). 반복해서 넘기거나, 401·403 을 주면 실패해요. 켜진 뒤 3초를 넘기면 그때마다 앱이 다시 시작될 수 있어요. - 고치는 법 —
/가 위 조건을 못 맞추면, 로그인 없이 바로 200 을 주는 가벼운 경로(예:/healthz)를 만들고axhub.yaml에runtime.health_path: /healthz로 적어요. 이 경로에서는 DB 를 부르지 않아요. - 처리 — 바로 고침
- 실패 코드 —
healthcheck.startup_budget_exceeded,healthcheck.container_crash
1-4. 주소 이름 검사가 상태 확인을 막지 않는다
- 무엇을 보나 — Django 의
ALLOWED_HOSTS처럼 들어온 요청의 주소 이름(Host)을 검사하는 설정. - 왜 안 되나 — 상태 확인 요청의 Host 는 앱 주소가 아니라 파드의 IP 예요. 목록에 없으면 400 을 줘서 확인이 실패해요.
- 고치는 법 — 상태 확인 경로는 Host 검사에서 빼거나, 허용 목록을 넓혀요 (Django 는
ALLOWED_HOSTS = ["*"]처럼, 또는 상태 확인용 미들웨어를 앞에 둬요). - 처리 — 바로 고침
- 실패 코드 —
healthcheck.startup_budget_exceeded,healthcheck.container_crash
1-5. 60초 안에 켜진다
-
무엇을 보나 — 시작할 때 오래 걸리는 일을 먼저 하는지 (대량 데이터 적재, 모델 내려받기, 오래 걸리는 마이그레이션). Next.js SSR 처럼 무거운 앱인지.
-
왜 안 되나 — 배포 뒤 60초 안에 상태 확인에 응답하지 않으면 실패로 봐요. 새 앱 기본 사양은 CPU 0.5코어라 무거운 앱은 켜지는 데 오래 걸릴 수 있어요. Dockerfile 의
HEALTHCHECK는 읽지 않아요. -
고치는 법 — 오래 걸리는 일은 시작 순서에서 빼요. 그래도 오래 걸리면
axhub.yaml에 대기 시간을 늘리거나(최대 5분,90s·2m처럼 단위 필수) 앱 사양을 올려요.runtime: healthcheck: start_period: 120s -
처리 — 바로 고침 (매번 데이터를 지우고 다시 넣는 시작 작업처럼 앱 동작이 바뀌면 물어봄)
-
실패 코드 —
healthcheck.startup_budget_exceeded
1-6. 포트는 하나, 평문 HTTP
- 무엇을 보나 — 앱이 직접 HTTPS 인증서로만 여는지, 관리자 화면을 다른 포트로 따로 여는지.
- 왜 안 되나 — HTTPS 는 AXHub 가 앞단에서 처리하고 앱은 평문 HTTP 로 받아요. HTTPS 로만 열면 상태 확인이 실패해요. 바깥에는 포트 하나만 열려서, 두 번째 포트의 화면은 배포는 되지만 접속할 수 없어요.
- 고치는 법 — HTTP 로 열고, 화면은 한 포트로 합쳐요. 서비스가 여럿이면 compose 를 써요.
- 처리 — 바로 고침
- 실패 코드 —
healthcheck.startup_budget_exceeded(HTTPS 로만 열 때), 없음 (두 번째 포트)
2. 빌드
2-1. 무엇으로 빌드할지 정해져 있어야 한다
- 무엇을 보나 — 저장소 맨 위에
Dockerfile이 있는지. 없다면 Node · Python · Go · Ruby · Java 중 하나인지. - 왜 안 되나 — AXHub 는
axhub.yaml→ 맨 위Dockerfile→ compose 파일 → 자동 감지 순서로 빌드 방법을 정해요. 자동으로 만든 Dockerfile 은 Node 는node:22-alpine, Python 은python:3.12-slim을 써요. 다섯 언어가 아니면(Rust, PHP, .NET 등) 자동 감지가 실패하거나, 감지돼도 도구 없는 이미지로 빌드돼 실패해요. 네이티브 모듈(better-sqlite3등)은 컴파일 도구(python3·make·g++)가 없어 실패할 수 있어요. - 고치는 법 — Dockerfile 을 직접 둬요. 네이티브 모듈이 필요하면 Dockerfile 에 컴파일 도구 설치를 넣거나 순수 JS 대안을 써요.
- 처리 — 바로 고침
- 실패 코드 —
resolve.spec_invalid(상세:dockerfile not found at repo root: railpack auto-detection failed),build.command_failed,build.native_module_toolchain_missing
2-2. 내 컴퓨터에서 빌드가 끝까지 된다
- 무엇을 보나 —
npm run build같은 빌드 명령이 오류 없이 끝나는지. 개발 서버(npm run dev)는 타입 오류가 있어도 돌지만 빌드는 멈춰요. 코드가 쓰는 패키지가package.json·requirements.txt에 다 있는지. - 왜 안 되나 — AXHub 는 빌드 명령을 그대로 실행해요. 타입 오류, 의존성 충돌(
ERESOLVE), 없는 명령(exit 127)은 서버에서도 실패해요. 의존성 목록에 없는 패키지를 쓰면 빌드는 되고 시작할 때 죽어요. 빌드는 20분을 넘길 수 없어요. - 고치는 법 — 배포 전에 빌드를 한 번 돌려 오류를 고쳐요. 빠진 패키지는 의존성 목록에 더해요. 의존성 충돌은 lockfile 을 커밋하고 같은 패키지 관리자를 써요.
- 처리 — 바로 고침
- 실패 코드 —
build.command_failed,build.dependency_install_failed,build.timeout,healthcheck.container_crash(빠진 패키지)
2-3. 빌드에는 실행용 환경변수가 없다
- 무엇을 보나 — 빌드 중에
DATABASE_URL같은 값을 읽는지 (예:prisma.config.ts가 빌드 때env("DATABASE_URL")를 요구). 직접 쓴 Dockerfile 이 빌드 값을ARG없이 쓰는지. - 왜 안 되나 — 실행용(runtime) 값은 빌드에 들어가지 않아요. 빌드에 필요한 값은 Stage 를
Build또는Both로 등록해야 하고, 직접 쓴 Dockerfile 은ARG 이름을 선언해야 읽혀요. - 고치는 법 — 빌드 때 쓰는 값은 Stage 를 Build/Both 로 등록하고 Dockerfile 에
ARG를 더해요. 빌드에 DB 주소가 꼭 필요하면(Prisma 등) 실제 DB 가 아닌 형식만 맞는 값을 Build 로 등록해요. 실행 중에는 AXHub 가 진짜 값으로 덮어요. - 처리 — 바로 고침
- 실패 코드 —
build.env_referenced_at_build_time,build.command_failed
2-4. 앱이 쓰는 파일은 저장소에 올라가 있다
- 무엇을 보나 — 앱이 읽는 데이터 파일이
.gitignore에 걸려 빠져 있는지. 마지막 수정이 커밋·push 됐는지. 100MB 넘는 파일,node_modules처럼 커밋된 대량 파일이 있는지. - 왜 안 되나 — 서버는 내 컴퓨터가 아니라 저장소의 코드를 가져가요. 파일 하나 100MB, 전체 200MB, 파일 5만 개를 넘으면 소스를 받지 못해요.
- 고치는 법 — 필요한 파일을 커밋하고 push 해요. push 직후라 커밋을 못 찾으면 잠시 뒤 다시 배포해요. 큰 데이터는 파일 보관함(
storage)이나 DB 에 둬요. - 처리 — 커밋·push 는 바로 고침, 큰 데이터 옮기기는 물어봄
- 실패 코드 —
resolve.commit_not_found,build.context_file_missing,prepare.source_package_failed,healthcheck.container_crash(시작할 때 파일을 읽다 죽음)
2-5. 배포 방식은 앱을 만들 때 정해지고 바꿀 수 없다
배포 방식은 셋이에요: docker(컨테이너 하나), compose(서비스 여러 개), static(정적 사이트). 앱을 만들 때 정해지고, 나중에 바꿀 수 없어요. 저장소 내용으로 판정한 방식이 앱의 방식과 다르면 배포가 실패해요.
- 무엇을 보나 — 로컬 DB 를 띄우려고 만든
docker-compose.yml이 맨 위에 있고 Dockerfile 이 없는지. 이미 만든 앱의 방식과 저장소 모양이 맞는지. - 왜 안 되나 — Dockerfile 이 없으면 compose 파일을 앱 전체로 보고 compose 로 판정해요. docker 로 만든 앱이면 방식이 어긋나 실패하고, 로컬 전용 DB 만 올라가요.
- 고치는 법 — 로컬 전용 compose 파일이면
docker-compose.dev.yml처럼 이름을 바꾸거나, 맨 위에 Dockerfile 을 둬요 (Dockerfile 이 있으면 compose 파일은 보지 않아요). 정말로 다른 방식이 필요하면 새 앱으로 다시 만들어야 해요. - 처리 — 파일 이름 바꾸기·Dockerfile 추가는 바로 고침. 새 앱으로 다시 만들기는 물어봄.
- 실패 코드 —
resolve.spec_invalid(상세:deploy method mismatch)
3. 설정
3-1. axhub.yaml 은 맨 위에, 아는 필드만
- 무엇을 보나 —
axhub.yaml이 하위 폴더에 있는지, 모르는 필드(오타 포함)가 있는지. - 왜 안 되나 — 저장소 맨 위 파일만 읽고, 모르는 필드가 하나라도 있으면 파일 전체를 거부해요. 크기는 8KB 까지예요.
- 고치는 법 — 앱이 저장소의 하위 폴더에 있으면 파일을 옮기지 말고, 맨 위
axhub.yaml에root: 하위폴더를 적어요. 필드는 axhub.yaml 레퍼런스에 있는 것만 써요.runtime.healthcheck의 시간은90s처럼 단위를 붙이고,ci.timeout은 숫자(초)로 써요. - 처리 — 바로 고침
- 실패 코드 —
resolve.spec_invalid(상세:axhub.yaml 파싱 실패또는axhub.yaml의 …)
3-2. 필요한 환경변수는 AXHub 에 등록돼 있다
- 무엇을 보나 — 코드가 읽는 환경변수(
process.env.X,os.environ["X"])와axhub.yaml의env.required가 AXHub 에 등록돼 있는지..env파일에만 있는지. - 왜 안 되나 —
.env파일은 서버로 올라가지 않아요.env.required에 적은 값이 없으면 배포가 시작 전에 멈추고, 적지 않은 값이 없으면 읽는 방식에 따라 켜지다 죽거나 그 기능만 실패해요. - 고치는 법 — 값을 AXHub 에 등록하고 다시 배포해요. 이름은 대문자·숫자·
_만,AXHUB_로 시작할 수 없어요. - 처리 — 내 컴퓨터의
.env에 값이 있으면 바로 등록해요. 단 내 컴퓨터를 가리키는 값(localhost,127.0.0.1, 사설 IP)과 3-3의 이름은 등록하지 않아요. 값을 어디서도 찾을 수 없으면 물어봄. - 실패 코드 —
prepare.required_env_missing,healthcheck.container_crash
3-3. AXHub 가 넣어 주는 값은 실행용으로 넣지 않는다
아래 이름은 조건이 맞으면 AXHub 가 실행 중인 앱에 자동으로 넣고, 같은 이름으로 등록한 값보다 우선해요. 빌드에는 들어가지 않아요.
| 이름 | 언제 들어오나 |
|---|---|
PORT | 항상 (Docker 앱) |
DATABASE_URL, DIRECT_DATABASE_URL | 그 환경에 AXHub DB 가 있을 때 (database.engine: postgres 로 만들어져요) |
STORAGE_ENDPOINT, STORAGE_BUCKET, STORAGE_ACCESS_KEY, STORAGE_SECRET_KEY | 파일 보관함이 있을 때 (storage.enabled: true 로 만들어져요) |
STORAGE_CDN_BASE_URL, STORAGE_CDN_SIGN_KEY | storage.cdn: true 일 때 |
AXHUB_RELAY_SIGNING_SECRET | 항상 |
AXHUB_APP_TOKEN, AXHUB_API_URL | 알림·메일 기능을 쓸 수 있을 때 |
- 처리 — 이 이름을
.env에서 옮겨 실행용으로 등록하지 않아요.env.required에도 적지 않아요. 빌드에 필요하면 2-3대로 Build 로 등록해요. 바로 고침.
4. 데이터 저장
4-1. AXHub DB 는 빈 상태로 시작한다
- 무엇을 보나 — 앱이 테이블을 만드는 코드(
CREATE TABLE IF NOT EXISTS, ORM 마이그레이션)가 있는지. 내 컴퓨터 DB 에서 손으로 만든 테이블에 기대고 있는지. - 왜 안 되나 — AXHub DB 는 운영·스테이징마다 따로, 빈 상태로 만들어져요. 테이블을 만드는 코드가 없으면 배포는 성공하고 첫 요청부터 500 이 나요.
- 고치는 법 — 앱이 켜질 때 테이블을 만들게 하거나(
CREATE TABLE IF NOT EXISTS), 마이그레이션을 시작 순서에 넣어요 (60초 안에 끝나야 해요). Prisma 같은 마이그레이션 도구는DIRECT_DATABASE_URL로 연결해요 —DATABASE_URL은 트랜잭션 단위 중계기라 마이그레이션 잠금이 동작하지 않아요. 두 주소 모두 SSL 을 쓰지 않으므로(sslmode=disable) SSL 을 강제하는 연결 설정은 빼요. - 처리 — 바로 고침
- 실패 코드 — 없음 (배포는 성공하고 요청이 500)
4-2. 서버 폴더에 저장한 것은 사라진다
-
무엇을 보나 — SQLite(
*.db,better-sqlite3,sqlite3), JSON 파일에 데이터를 쌓는 코드(lowdb 등), 업로드를 앱 폴더(./uploads)에 저장하는 코드. -
왜 안 되나 — Docker 앱의 일반 폴더는 다시 배포하거나, 앱이 재시작되거나, 절전에서 깨어나면 처음 상태로 돌아가요.
-
고치는 법 — 데이터는 AXHub DB(PostgreSQL)로, 파일은 파일 보관함으로 옮겨요.
database: engine: postgres # DATABASE_URL 이 자동으로 들어와요 storage: enabled: true # STORAGE_* 가 자동으로 들어와요 -
처리 — 물어봄 ("AXHub DB 로 바꿀까요, 그대로 올릴까요?"). 보관이 필요 없는 캐시, 테스트 코드에서만 쓰는 SQLite 는 그대로 둬요.
-
실패 코드 — 없음 (배포는 성공하고 데이터만 사라져요)
4-3. DB 는 PostgreSQL 하나
- 무엇을 보나 — MySQL·MongoDB 를 쓰는지, 지원하지 않는 확장을 쓰는지.
- 왜 안 되나 — AXHub DB 는 PostgreSQL 만 돼요. 확장은 pgvector, pg_trgm, citext, unaccent, fuzzystrmatch, uuid-ossp, pgcrypto, hstore, ltree, intarray, tablefunc, btree_gin, btree_gist 만 쓸 수 있어요.
- 고치는 법 — PostgreSQL 로 옮기고, 확장은
database.extensions에 적어요. - 처리 — 물어봄
- 실패 코드 —
resolve.spec_invalid(목록에 없는 확장),prepare.raw_db_extension_failed(설치 실패)
5. 네트워크
5-1. 밖으로는 널리 쓰는 포트만 나간다
-
무엇을 보나 — 메일 서버 25번 포트로 직접 발송, 아래 목록에 없는 포트(예: 8080, 9200) 로 외부 연결, 사설 IP(10.x, 172.16~31.x, 192.168.x)·
localhost의 다른 프로그램 호출. -
왜 안 되나 — 보안을 위해 앱에서 밖으로 나가는 연결은 공인 인터넷의 아래 TCP 포트만 열려 있어요. 막힌 연결은 오류 없이 시간 초과로 끝나요. 시작할 때 연결하면 앱이 켜지지 못하고, 요청 중에 연결하면 그 기능만 실패해요.
용도 포트 웹 443, 80 메일 보내기 465, 587 (25 는 막혀 있어요) 메일 받기 993, 995 SSH·SFTP 22 외부 DB 3306 (MySQL), 5432 (PostgreSQL), 6379 (Redis), 27017 (MongoDB) 이 포트는 앱을 다시 배포할 때 적용돼요. 2026-10-16 전에 마지막으로 배포한 앱은 443 만 열려 있으니 한 번 다시 배포해요.
-
고치는 법 — 메일은 465·587 로 보내요(nodemailer 의
port: 587등). 데이터는 가능하면 AXHub DB 를 쓰고, 외부 DB 를 꼭 써야 하면 그 서비스가 공인 주소로 접속을 허용하는지 확인해요. 회사 내부망·내 컴퓨터는 연결할 수 없어요 (8절). -
처리 — 물어봄 (연결 대상을 바꾸는 일이라). 바꿀 수 없으면 알림.
-
실패 코드 —
healthcheck.startup_budget_exceeded,healthcheck.container_crash(시작할 때 연결하면), 없음 (요청 중에 연결하면)
5-2. 요청 크기와 응답 시간
- 무엇을 보나 — 요청 하나로 50MB 넘는 파일을 받는지, 요청 하나 안에서 2분 넘게 걸리는 일을 하는지(긴 AI 일괄 처리, 큰 리포트 생성).
- 왜 안 되나 — 요청 본문은 50MB 까지, 첫 응답은 120초 안에 와야 해요. 넘으면 413·504 로 끊겨요.
- 고치는 법 — 큰 파일은 파일 보관함에 바로 올리게 하고(미리 서명된 주소), 오래 걸리는 일은 "작업 시작 → 진행 상황 조회"로 나누거나 응답을 조금씩 흘려보내요.
- 처리 — 물어봄
- 실패 코드 — 없음 (런타임 413·504)
5-3. 비공개 앱은 외부 웹훅을 직접 받지 못한다
-
무엇을 보나 — 결제·GitHub·메신저 같은 외부 서비스가 앱으로 요청을 보내는 경로(
/api/webhook,/hooks/…, Stripe·Slack 서명 검증 코드). -
왜 안 되나 — 비공개 앱 주소로 오는 요청은 모두 AXHub 로그인을 거쳐요. 로그인하지 않은 외부 서버의 요청은 로그인 화면으로 넘겨져 앱까지 오지 않아요. 배포는 성공하고 웹훅만 도착하지 않아요.
-
고치는 법 — 접수창구를 만들어 그 주소를 외부 서비스에 알려 줘요. 앱은 비공개 그대로 두고, 검증을 통과한 요청만 앱의 수신 경로로 전달돼요. 전달된 요청은
AXHUB_RELAY_SIGNING_SECRET으로 서명을 확인해요 (웹훅 받기).axhub relay create --app <앱> --name <창구-이름> --verify-mode key --target-path /api/webhook -
처리 — 물어봄 (외부 서비스에 새 주소를 등록해야 해서)
-
실패 코드 — 없음 (배포는 성공하고 웹훅만 안 와요)
5-4. AI 는 AXRouter 로 부른다
- 무엇을 보나 — Ollama·LM Studio 같은 로컬 AI 서버 주소(
localhost:11434), 로컬 임베딩·음성 인식(whisper.cpp) 호출, 회사에서 쓰는 AI 키가 따로 없는지. - 왜 안 되나 — 서버에는 로컬 AI 가 없어요 (8절). 회사가 AXRouter 연동을 켜 두었으면, 개인 AI 키 없이 회사 AI 게이트웨이(OpenAI 호환)로 부를 수 있어요.
- 고치는 법 — 먼저 그 회사가 AXRouter 를 쓰는지 확인해요.
axhub axrouter status --tenant <회사> --json의data.available이에요.-
true— 키를 발급해 비밀 환경변수로 등록하고, 앱은 OpenAI 호환 방식으로 불러요. 대화(/chat/completions)·임베딩(/embeddings)·받아쓰기(/audio/transcriptions) 모두 같은 주소예요.axhub axrouter keys issue --name "<앱> 서버용" # 키는 이때 한 번만 보여요 axhub env set LLM_API_KEY --app <앱> --secret --stage runtime --from-stdin axhub env set LLM_BASE_URL https://axrouter.ai/v1 --app <앱> --plain --stage runtime쓸 수 있는 모델 이름은
GET $LLM_BASE_URL/models로 봐요. 사용량은 키를 발급한 사람의 AI 예산으로 잡혀요. -
false또는 확인 실패 — AXRouter 를 권하지 않아요(회사가 연동을 꺼 둔 경우예요). 사용자가 쓰는 AI 서비스의 본인 키를 비밀 환경변수로 넣어요.axhub env set OPENAI_API_KEY --app <앱> --secret --stage runtime --from-stdin # 또는 ANTHROPIC_API_KEY 등
-
- 처리 — 물어봄 (어느 AI 로 바꿀지, 누구 키로 쓸지)
- 실패 코드 — 없음 (그 기능만 실패해요)
6. 정적 사이트
-
무엇을 보나 — 서버 없이 HTML·JS 만 올리는 사이트인지, 그리고 앱을 정적 사이트로 만들었는지.
-
왜 안 되나 — 정적 호스팅은 앱을 정적 사이트(static)로 만들었을 때만 써요. 빌드 결과 폴더(기본
dist)에index.html이 있어야 하고, 파일 2,000개·파일당 50MB·합계 500MB 까지예요. DB·파일 보관함은 쓸 수 없어요. docker 로 만든 앱에deploy_method: static을 적으면 방식이 어긋나 실패해요 (2-5). -
고치는 법 — 정적 앱이면 아래처럼 적어요. docker 로 만든 앱이라면 그대로 docker 로 둬도 Vite 같은 사이트는 자동 감지로 떠요.
build: deploy_method: static static_output_dir: dist빌드 없이 루트에
index.html이 있는 사이트는static_output_dir: .이에요. 작업공간이 Axhub 저장소를 쓰면 이 설정을 담은 소스를 앱 저장소에 push해서 배포해요 — 저장소 화면에서 코드와 커밋별 배포 결과를 볼 수 있어요. -
처리 — 정적 앱의 설정 맞추기는 바로 고침. 방식을 바꾸는 일은 물어봄.
-
실패 코드 —
resolve.spec_invalid(상세:deploy method mismatch),apply.static_release_failed,prepare.database_unsupported_for_static,prepare.storage_unsupported_for_static
7. 리소스와 절전
-
사양 — 새 앱 기본 사양은 CPU 0.5코어·메모리 1GB 예요. 메모리를 넘으면 앱이 강제로 재시작돼요(
healthcheck.container_crash). 브라우저(PDF 만들기)·OCR·큰 파일 처리처럼 무거운 일을 하면 사양을 올려요. 실행 중인 앱에 바로 반영돼요 (최대 16GB, 회사가 정한 상한이 있으면 그 아래까지).axhub apps update <앱> --resource-tier M # XS 0.5코어·1GB · S 1코어·2GB · M 2코어·4GB · L 3코어·8GB · XL 4코어·16GB -
임시 저장공간 — 기본 1GB 예요. 시작할 때 큰 파일을 내려받으면 넘칠 수 있어요.
runtime.ephemeral_storage: 4Gi처럼 최대 10GB 까지 늘릴 수 있어요. -
예약 작업 — AXHub 에는 따로 예약 실행(cron) 기능이 없어요. 앱 안에서 돌려요 — Node 는
node-cron, Python 은 APScheduler 처럼 앱이 켜질 때 스케줄러를 같이 띄워요. 레플리카가 2 이상이면 작업이 두 번 돌아요. compose 앱이면 스케줄러 서비스를 하나 두고 그 서비스만 레플리카 1로 둬요 (레플리카). 재시작 중에 놓친 실행을 앱이 켜질 때 따라잡게 해 두면 안전해요. -
절전 — 운영 앱은 72시간 동안 아무도 접속하지 않으면 절전될 수 있어요. 절전 중에는 앱 안의 예약 작업(cron)·백그라운드 작업도 멈춰요. 다음 접속 때 깨어나요.
-
처리 — 알림
8. AXHub 에서 안 되는 6가지
아래는 구조상 AXHub 서버에서 할 수 없는 일이에요. 코드에서 흔적을 찾으면 사용자에게 알리고, 대신 쓸 방법을 물어요.
| 앱에 이런 기능이 있으면 | 코드에서 보이는 흔적 | 대신 이렇게 |
|---|---|---|
| 내 컴퓨터의 파일·프로그램을 사용 | C:\, /Users/, 바탕화면 경로, 앱 자신이 아닌 localhost:포트 호출 | 파일을 저장소에 넣거나 화면에서 업로드받기 |
| 회사 내부 시스템에 연결 | 사설 IP, .local·.internal·.corp 주소, 인트라넷 주소 | 예시 데이터를 앱에 넣어 사용 |
| 앱 안에서 AI 모델을 직접 실행 | Ollama, llama.cpp, vLLM, transformers 모델 내려받기, diffusers, GPU 코드 | AXRouter 같은 AI API 로 호출 (5-4) |
| 웹사이트가 아닌 형태의 앱 | Electron·Tkinter·PyQt, React Native·Expo·Flutter, 크롬 확장 manifest.json, 엑셀 매크로 | 같은 기능을 웹 화면으로 |
| 엑셀·한글 프로그램을 열어 작업 | win32com, pywin32, xlwings, comtypes, 한글 자동화, AppleScript | 파일만 읽고 쓰는 라이브러리(openpyxl 등) |
| 입력한 내용을 서버 파일에 저장 | 4-2 참고 | AXHub DB · 파일 보관함 |
- 처리 — 처음 발견하면 물어봄. 사용자가 "그대로 올려"라고 하면 기억해서 다시 묻지 않아요. 테스트·예시 코드·README 에만 있는 흔적은 무시해요.
9. 실패 코드로 규칙 찾기
배포가 실패하면 실패 코드로 위 규칙을 찾아요.
| 실패 코드 | 먼저 볼 규칙 |
|---|---|
resolve.commit_not_found | 2-4 (커밋을 push 했는지, push 직후라면 잠시 뒤 다시) |
resolve.spec_invalid | 2-1, 2-5, 3-1, 4-3, 6 (상세 문구를 같이 봐요) |
prepare.required_env_missing | 3-2, 3-3 |
prepare.source_package_failed | 2-4 |
prepare.database_unsupported_for_static, prepare.storage_unsupported_for_static | 6 |
prepare.raw_db_extension_failed | 4-3 |
build.command_failed, build.dependency_install_failed, build.timeout | 2-2, 2-1 |
build.native_module_toolchain_missing | 2-1 |
build.env_referenced_at_build_time | 2-3 |
build.context_file_missing, build.dockerfile_parse_error | 2-4 (Dockerfile 의 COPY 경로·.dockerignore), Dockerfile 문법 |
ci.tests_failed | axhub.yaml 의 ci.commands 가 실패했어요 — 설치 명령을 앞에 뒀는지 |
healthcheck.startup_budget_exceeded | 1-1, 1-2, 1-3, 1-4, 1-6, 5-1 을 먼저, 그다음 1-5 |
healthcheck.container_crash | 2-2(빠진 패키지), 2-4(빠진 파일), 3-2, 5-1, 7(메모리), 1-3 |
healthcheck.rollout_timeout | 앱이 켜졌지만 5분 동안 준비 상태가 안 됐어요 — 1-3 (켜진 뒤 3초) |
healthcheck.capacity_wait_timeout | 앱 문제가 아니에요. 잠시 뒤 다시 배포해요 |
healthcheck.image_pull_failed | compose 의 image: 이름·접근 권한 |
apply.static_release_failed | 6 |