사람함께에이전트▾
사람 — 직접 정하고 책임지는 비즈니스 규칙과 흐름. 직접 읽어보세요.
함께 — 개념은 알아두고, 세부 규칙은 에이전트가 따릅니다.
에이전트 — 에이전트가 따르는 규칙과 레퍼런스. 필요할 때 찾아보세요.
Docker
빌드된 앱 하나와, 공장 한쪽에 놓인 작은 기기가 있다고 해 봅시다. 정전 뒤에도 데이터를 그대로 지닌 채 다시 올라와야 하고, 왜 멈췄는지도 볼 수 있어야 합니다.
이런 작은 edge 서버는 Akan 앱 컨테이너 하나로 시작합니다. 이미지의 기본값은 다음과 같습니다.
이미지 기본값그래서 할 일
PORT=8282
앱이 8282 포트에서 요청을 받으므로
8282:8282로 게시합니다./workspace/sqlite
sqlite 파일이 여기 놓이므로, 재시작해도 남도록 볼륨을 마운트합니다.
AKAN_LOG_TO_FILE=0
파일 로깅이 꺼져 있으므로, stdout을 수집하거나 1로 켜고 로그 볼륨을 마운트합니다.
ca-certificatestzdata
설치되는 패키지는 이 둘뿐이므로, ffmpeg이나 Chromium은
docker.preRuns에 적습니다.console.js
main.js 옆에 들어 있으므로 docker exec로 운영 콘솔을 엽니다.파일 로깅을 끈 이유: 컨테이너의 쓰기 레이어는 컨테이너와 함께 사라지므로, 파일은 디스크만 채웁니다. 로그는 stdout으로 수집합니다.
최소 compose 파일
앱 하나를 위한 단순한 compose 파일입니다.
myapp과 이미지 이름을 자기 앱에 맞게 바꾸세요.docker-compose.yaml
restart: unless-stopped는 정전이나 재부팅 뒤에 컨테이너를 다시 띄웁니다.- 볼륨 두 개가 sqlite 파일과 로그 파일을 호스트에 남깁니다.
AKAN_PUBLIC_OPERATION_MODE: edge는 온프레미스 기기라는 표시입니다. 이미지 기본값은cloud입니다.AKAN_DATABASE_MODE: single은 모든 데이터를 볼륨의 SQLite 파일에 둡니다. 앱이single만 선언했다면 빼도 됩니다.


포트의 컨테이너 쪽은 80이 아니라 8282입니다.
AkanApp은 PORT에 바인딩하므로 이미지 안에서 80 포트로 요청을 받는 프로세스는 없습니다. "8282:8282"로 매핑하거나 PORT: 80을 함께 주세요.컨테이너 환경변수
이미지에는 빌드 때의 값이 이미 들어 있습니다. 그래서 컨테이너에는 달라지는 값만 주면 됩니다.
필수 — 빌드가 채워 둠
이 셋이 없으면 앱이 뜨지 않습니다.
AKAN_PUBLIC_APP_NAMEstring기본값 빌드 값
앱의 코드네임입니다.
AKAN_PUBLIC_REPO_NAMEstring기본값 빌드 값
workspace 이름입니다.
AKAN_PUBLIC_SERVE_DOMAINstring기본값 빌드 값
앱이 자기 origin을 만들어 내는 기준 도메인입니다.
자주 바꾸는 값
AKAN_PUBLIC_ENVlocal | testing | debug | develop | main기본값 빌드 값 (debug)
어느 배포인지입니다. Helm chart는 네임스페이스마다 이 값을 넣습니다. 이미지에는 빌드할 때 고른 환경의 서버 env만 들어 있으므로, 그 환경으로만 실행됩니다.
AKAN_PUBLIC_OPERATION_MODElocal | edge | cloud | module기본값 이미지에서는 cloud
어디서 도는지입니다. 이 페이지 예시의 온프레미스 기기는
edge입니다.AKAN_DATABASE_MODEsingle | multiple | cluster기본값 앱이 선언한 유일한 모드
database.modes에 선언한 모드 중 하나를 고릅니다. 앱이 여러 모드를 선언했다면 꼭 줘야 합니다.PORTnumber기본값 8282
gateway 또는 단독(solo) 프로세스가 바인딩하는 포트입니다.
AKAN_SQLITE_DIRstring기본값 이미지에서는 /workspace/sqlite
sqlite 파일이 놓이는 곳입니다. 마운트한 볼륨을 가리키게 하세요.
AKAN_LOG_TO_FILE0 | 1기본값 이미지에서는 0
순환 로그 파일입니다. 1로 두고
AKAN_LOG_DIR을 마운트하면 다시 켜집니다.AKAN_LOG_DIRstring기본값 이미지에서는 /workspace/runtime/logs
파일 로깅을 켰을 때 순환 로그 파일이 놓이는 곳입니다.
AKAN_CONSOLE1기본값 미설정
production 계열 환경에서
console.js를 열 때 필요합니다.데이터가 있는 곳
데이터와 업로드 파일이 어디 있는지 정합니다. 컨테이너에 주면
env.server.ts에서 번들된 같은 값보다 우선하므로, 이미지 하나로 모든 배포를 돌릴 수 있습니다.SQLITE_DATABASE_PATHstring기본값 <AKAN_SQLITE_DIR>/<app>-<env>.dbsingle · multiple
SQLite 데이터베이스 파일입니다.
multiple에서는 호스트의 모든 컨테이너가 이 파일 하나를 엽니다.AKAN_SOLID_DB_PATHstring기본값 <AKAN_SQLITE_DIR>/<app>-<env>_solid.dbsingle
캐시, 큐, pubsub을 담는 SQLite 파일입니다.
REDIS_URIstringmultiple · cluster
캐시, 큐, pubsub을 맡는 Redis입니다. 꼭 필요하며,
rediss://로 쓰면 TLS로 연결합니다.POSTGRES_URLstringcluster
Postgres 데이터베이스입니다. 커넥션 풀 크기, SSL, prepared statement는 쿼리 문자열로 정합니다.
AKAN_STORAGE_SHAREDtruemultiple · cluster
업로드가 쌓이는
/workspace/local이 모든 인스턴스가 함께 마운트한 볼륨 하나라고 알립니다.- transaction 모드의 PgBouncer 뒤에서는
&prepare=false도 그 쿼리 문자열에 더합니다. - Postgres는 값을 나눠서 줄 수도 있습니다. URL이 없으면
POSTGRES_HOST,POSTGRES_PORT,POSTGRES_DATABASE(또는POSTGRES_DB),POSTGRES_USER,POSTGRES_PASSWORD를 읽습니다.


USE_AKANJS_PKGS는 컨테이너 변수가 아닙니다. CLI가 읽는 workspace 개발용 플래그라서, 배포에 설정해도 아무 일도 일어나지 않습니다.AKAN_REPLICA로 확장
AKAN_REPLICA는 쉼표로 구분한 세 개의 수이고, 자리마다 역할이 정해져 있습니다. 프로세스를 몇 개 띄울지, 그 앞에 gateway를 둘지를 함께 정합니다.이 페이지에서 쓰는 말
용어설명
replica
요청을 처리하거나, 백그라운드 작업을 돌리거나, 둘 다 하는 서버 프로세스 하나입니다.
gateway
PORT를 잡고 들어온 트래픽을 replica들에 나눠 주는 앞단 프로세스입니다.
solo
gateway 없이 replica 하나가 컨테이너의 유일한 프로세스로 도는 상태입니다.
internal
시그널이 선언하는 백그라운드 작업입니다. cron, interval, 큐 작업 등이 있습니다.
RSC worker
페이지를 렌더링하는 별도 프로세스입니다. 웹을 서빙하는 replica마다 하나씩 붙습니다.
세 자리
| 자리 | 역할 | 기본값 |
|---|---|---|
| ↳ 하는 일 | ||
| 1 | federation | 0 |
요청을 처리합니다. serverMode: "batch"로 고정한 internal은 돌리지 않습니다. | ||
| 2 | batch | 0 |
| internal만 돌리고 요청은 받지 않습니다. 하나라도 있으면 gateway가 반드시 남습니다. | ||
| 3 | all | 1 |
| 요청 처리와 internal을 모두 맡는 범용 replica입니다. | ||
값 예시
컨테이너가 요청을 처리하는지,
serverMode: "batch"로 고정한 internal이 도는지를 값별로 정리했습니다.AKAN_REPLICA
요청 처리
batch internal
serverMode: "batch"
solo — 프로세스 하나, gateway 없음
(미설정)
✓
✓
"0,0,1"과 같습니다. 범용 replica 하나입니다."1,0,0"
✓
federation replica 하나입니다.
"0,0,0"
✓
✓
범용 replica 하나로 바뀝니다. 0개를 요청할 수는 없습니다.
gateway가 앞에 있음
"2"
✓
federation replica 두 개입니다. 빠진 자리는 0으로 칩니다.
"0,1,0"
✓
batch replica 하나입니다. health check에 응답하도록 gateway가 남습니다.
✓처리함처리 안 함
solo 또는 gateway
프로세스 하나, 또는 gateway와 자식 프로세스
total = 1이고 batch = 0?
Solo: 컨테이너의 유일한 프로세스PORT에 바인딩
Gateway: PORT를 잡고 프록시
RSC worker
federation 자식unix socket
batch 자식요청을 받지 않음
RSC worker
total = 1이고 batch = 0?
예 · 아니오 · AKAN_SOLO=false · akan start
Solo: 컨테이너의 유일한 프로세스PORT에 바인딩
Gateway: PORT를 잡고 프록시
RSC worker
federation 자식unix socket
batch 자식요청을 받지 않음
RSC worker
- solo는 gateway를 건너뜁니다. 요청 처리 replica가 하나면 나눌 대상이 없으므로, 그 replica가 프록시 홉 없이 컨테이너의 유일한 프로세스로 돕니다.
- gateway 되살리기.
AKAN_SOLO=false(또는0)를 주세요.true는 아무 효과가 없습니다.akan start와,new AkanApp(...)에replica를 넘긴 경우에도 gateway가 남습니다. - solo는 probe에 직접 응답합니다.
/_akan/app/health,/_akan/app/metrics,/_akan/bench/ping이 gateway와 같은 형태로 돌아옵니다. - solo 프로세스를 감시하는 것은 오케스트레이터의 probe뿐입니다.
한 호스트에 컨테이너 여러 개
컨테이너 하나로 모자라면 같은 호스트에서 여러 개를
multiple 데이터베이스 모드로 띄웁니다. 호스트 볼륨의 SQLite 파일 하나를 함께 열고, 캐시·큐·pubsub은 Redis 하나를 같이 씁니다.| 모드 | 데이터베이스 | 캐시 · 큐 · pubsub |
|---|---|---|
| ↳ 도는 곳 | ||
| single | SQLite 파일 | SQLite 파일 |
| 컨테이너 하나 | ||
| multiple | 호스트 볼륨의 SQLite 파일 하나 | Redis |
| 호스트 하나의 여러 컨테이너 | ||
| cluster | Postgres | Redis |
| 여러 서버 | ||
앱의 빌드가 이 모드를 선언해야 합니다. 예를 들어
akan.config.ts에 database: { modes: ["multiple"] }를 적습니다. 그다음 이런 compose 파일로 이미지를 띄웁니다:docker-compose.yaml
- 모든 replica가 데이터베이스 파일 하나를 씁니다.
SQLITE_DATABASE_PATH가 컨테이너마다app-data볼륨의 같은 파일을 가리킵니다. REDIS_URI는 꼭 필요합니다. 캐시, 큐, pubsub이 그 Redis에 있고, 배포된 앱에는 대신 쓸 기본값이 없습니다.- 업로드 파일은 모든 replica가 마운트한 볼륨에 둡니다.
app-files를/workspace/local에 붙이고AKAN_STORAGE_SHARED로 알립니다. 이것도 오브젝트 스토리지도 없으면 앱은 컨테이너 하나의 디스크에 파일을 두기를 거부합니다. - 앞단의 리버스 프록시가 replica들에 요청을 나눕니다. replica는 따로 포트를 게시하지 않습니다.


SQLite 파일은 호스트 자신의 디스크에 둡니다. 같은 호스트의 컨테이너는 named volume이나 bind mount로 이 파일을 함께 쓸 수 있지만, NFS 같은 네트워크 파일시스템으로는 안 됩니다. 호스트가 여럿이면
cluster를 씁니다.웹 기능 덜어내기
API만 응답하는 배포에는 웹 절반이 필요 없습니다. 빌드에서 빼면 이미지가 줄고, 부팅 때 끄면 프로세스가 줄어듭니다.
설정
SSR
CSR
akan.config.ts — 빌드가 이미지에 넣는 것
web: true
✓
✓
기본값입니다. 두 가지를 모두 빌드합니다.
web: { csr: false }
✓
모바일 SPA 번들만 빼고 SSR은 남깁니다.
web: false
API 전용입니다. 라우트 산출물, CSR 번들, RSC worker 진입점,
public/이 모두 빠집니다.컨테이너 env — 부팅 때 좁히기
AKAN_CSR=false
✓
모바일 SPA 번들만 내립니다.
AKAN_SSR=false
RSC worker와 렌더 라우트를 내리고, CSR도 함께 내립니다.
✓제공꺼짐
- env는 좁히기만 합니다.
false나0으로 끌 수는 있지만, 빌드에서 뺀 것을 다시 켤 수는 없습니다. - SSR 없는 CSR은 없습니다. CSR 번들은 SSR 빌드가 만든 스타일시트를 품고 있으므로,
AKAN_CSR이 무엇이든AKAN_SSR=false는 CSR까지 내립니다. web: false는 이미지를 줄입니다. 이 문서 앱에서 재 보니 86MB가 6.2MB로 줄었습니다.
요청 처리 replica 하나만 도는 API 전용 컨테이너는 이렇게 띄웁니다.
Terminal
AKAN_SSR=false는 RSC worker 프로세스를 없앱니다. 이 문서 앱에서는 프로세스 3개 350MB가 2개 120MB로 줄었습니다."1,0,0"은 federation replica 하나입니다.serverMode: "batch"로 고정한 internal은 이 컨테이너에서 돌지 않습니다.
이미지 구성하기
Dockerfile은 직접 쓰지 않습니다.
akan.config.ts의 docker 키로 이미지를 정합니다. 이미지가 설치하는 것은 ca-certificates와 tzdata뿐이라서, ffmpeg이나 Chromium이 필요한 앱은 직접 적어야 합니다.imagestring | { amd64?, arm64? }기본값 oven/bun:1-slim
베이스 이미지입니다. 객체 형태는 아키텍처마다 고르고, 빠진 아키텍처는 기본값을 씁니다.
preRuns(string | { amd64?, arm64? })[]기본값 []
bun install 전에 실행할 명령입니다. 네이티브 의존성에 필요한 시스템 패키지 설치 등이 들어갑니다.postRuns(string | { amd64?, arm64? })[]기본값 []
bun install 뒤, 앱 파일을 복사하기 전에 실행할 명령입니다.commandstring[]기본값 ["bun", "main.js"]
컨테이너의
CMD입니다.ffmpeg이 필요하고, arm64에서만 도는 단계가 하나 있는 앱의 예시입니다.
apps/myapp/akan.config.ts
RUN없이 명령만 적습니다. Akan이 단계마다RUN을 붙이고, 객체 형태는TARGETARCH가 맞을 때만 실행합니다.- lib도 자기 단계를 보탭니다. lib을 마운트한 앱은 그 단계를 앞쪽에 중복 없이 물려받습니다. lib이 베이스 이미지나 command를 고르지는 않습니다.
- 안 쓰는 폰트는 덜어냅니다.
assets.pruneFonts는 기본으로 켜져 있고,dist에 복사된public/에서만 덜어냅니다. 원본 트리는 건드리지 않습니다. keepFonts는 자기public/기준입니다. glob은 그것을 선언한 앱이나 lib의 폴더 기준입니다. lib은assets중 이 키만 정할 수 있습니다.


Dockerfile 문자열은 lib 단계를 모두 버립니다.
docker에 Dockerfile 전체를 문자열로 쓰면 그대로 쓰이고, lib이 보탠 단계는 조용히 사라집니다. 파일 전체가 꼭 필요한 게 아니라면 객체 형태를 쓰세요.콘솔 열기
이미지에는
main.js 옆에 console.js가 들어 있어서, 컨테이너 안에 파일을 만들지 않고도 운영 콘솔을 열 수 있습니다. AKAN_CONSOLE=1은 exec 명령에만 주세요.Terminal
- 플래그가 필요한 이유.
AKAN_PUBLIC_ENV가main이거나, 운영 모드가cloud·edge이거나,NODE_ENV가production이면 콘솔이 열리지 않습니다. - 그래서 이미지에서는 늘 필요합니다. 이미지가
NODE_ENV=production과AKAN_PUBLIC_OPERATION_MODE=cloud를 넣어 두기 때문입니다. compose 파일에는 넣지 말고, exec 한 번에만 콘솔이 열리게 하세요. - 앱의 백그라운드 작업을 두 번 돌리지 않습니다. 콘솔 프로세스는 서비스는 띄우지만 internal 작업과 큐 worker는 돌리지 않으므로, 그 일은 실행 중인 컨테이너가 계속 맡습니다.
꿀팁
- 처음 compose 파일은 단순하게. 앱이 정말 필요로 할 때만 서비스를 더하세요.
- edge 장비를 바꾸기 전에 sqlite 볼륨을 백업하세요. 그 안에
<app>-<env>.db와<app>-<env>_solid.db가 있습니다. - 재시작이 반복되면 stdout을 보세요. 마운트한 폴더가 아니라
docker logs myapp을 읽으세요. 직접 켜지 않았다면 이미지의 파일 로깅은 꺼져 있습니다.
이어서 읽기