콘텐츠로 이동

빠른 시작

이 페이지는 windforce를 로컬에 띄우고 첫 잡을 5분 안에 돌리는 가장 짧은 경로를 보여 준다. docker compose로 스택을 기동하고, 워크스페이스를 부트스트랩하고, 간단한 hello 액션을 sync한 다음, curl로 잡을 실행하고 결과·로그를 받아 온다.

처음 한 번만 하면 되는 설치(스택 기동)와, 그 위에서 반복하는 작업(액션 작성 → sync → 실행)을 차례로 다룬다.

사전 준비

  • Docker / Docker Compose — 스택 전체(Postgres·server·worker)를 컨테이너로 띄운다.
  • 액션 소스를 담을 git 저장소 — 아래의 windforce.jsonmain.ts를 커밋해 둔 repo 하나(로컬·원격 어느 쪽이든, server·worker 컨테이너가 clone할 수 있으면 된다).
  • curl 정도의 HTTP 클라이언트.

호스트 5432 포트가 비어 있어야 한다 — 따로 떠 있는 Postgres가 있으면 먼저 내린다.

1. 스택 기동

레포 루트에서 한 줄로 전체 스택을 올린다. Postgres, DB 뷰어(Adminer), 1회성 마이그레이션·super-admin 시드, server(API·sync·reaper), worker가 함께 뜬다.

docker compose up -d --build
  • APIhttp://localhost:8080
  • 콘솔(웹 UI) — 같은 http://localhost:8080 (브라우저로 접속해 로그인). 첫 설치 super-admin은 compose 파일의 SUPERADMIN_EMAIL / SUPERADMIN_PASSWORD(root@demo.test / correct-password)로 들어간다.
  • Adminer(DB 뷰어)http://localhost:8081 (server postgres, user·password 모두 windforce).

이 빠른 시작은 curl 중심으로 진행한다. 콘솔로 같은 일(앱·액션 보기, 잡 결과 확인)을 할 수도 있다 — 콘솔 사용법은 사용자 가이드를 참고한다.

2. 워크스페이스 부트스트랩

server 컨테이너에 부트스트랩 CLI가 내장돼 있다. 워크스페이스를 만들고(첫 admin도 함께 생성), 토큰을 발급하고, 액션 소스 repo를 git source로 등록한 뒤 sync한다.

# 워크스페이스 + 첫 admin 생성
docker compose exec server /app/windforce bootstrap-workspace demo "Demo" \
  --admin-email dev@demo.test --admin-password correct-password --admin-username dev

# API 호출용 토큰 발급 (기본 scope "*")
docker compose exec server /app/windforce create-token demo dev@demo.test --admin

# 액션 소스가 있는 repo를 git source로 등록 (이름 "repo", 브랜치 main)
docker compose exec server /app/windforce add-gitsource demo repo <git-url> main

# clone → manifest 읽기 → 소스 materialize → 카탈로그 upsert
docker compose exec server /app/windforce sync demo 1
  • bootstrap-workspace <key> "<이름>" — 워크스페이스 demo를 만들고 그 안에 첫 admin 계정을 둔다.
  • create-token — 출력된 토큰 문자열을 다음 단계의 curl에서 Authorization: Bearer <token>으로 쓴다.
  • add-gitsource demo repo <git-url> main — sync가 사용할 repo·브랜치를 등록한다. <git-url>을 액션 소스 repo로 바꾼다. 등록된 첫 git source의 id는 1이다.
  • sync demo 1 — git source 1을 clone해 windforce.json(manifest)과 companion 스키마를 읽고, 소스를 오브젝트 캐시에 materialize한 뒤 app·action 카탈로그에 upsert한다. sync는 manifest와 스키마 파일만 읽고 코드(TS 등)는 파싱하지 않는다.

3. hello 액션 작성

sync 대상 repo에 아래 두 파일을 둔다. windforce.json이 app(Application Project)의 entrypoint·기본값을 선언하고, main.tsctx를 받는 main 함수를 export한다.

windforce.json (app당 1개, repo의 app 루트에 위치):

{
  "app": "greet",            // app_key — 실행 주소의 {app}
  "entrypoint": "main.ts",   // app당 1개. 모든 action이 공유
  "timeout": 60,
  "tag": "default",          // 잡을 default 워커 클래스로 라우팅
  "actions": {
    "hello": {}              // action_key — 실행 주소의 {action}
  }
}

main.ts (entrypoint = 조립):

import type { WindforceContext } from "windforce-client"

export async function main(ctx: WindforceContext) {
  const input = (ctx.input ?? {}) as { name?: string }
  ctx.logger.info(`hello ${input.name ?? "world"}`)   // 로그 채널
  return { message: `hi ${input.name ?? "world"}` }    // JSON 직렬화 가능한 결과
}

핵심 계약 몇 가지:

  • 입력은 ctx.input으로만 온다 — env나 입력 파일을 직접 읽지 않는다. 타입은 unknown이라 저자가 자기 타입으로 좁힌다.
  • 결과는 return — JSON 직렬화 가능한 값이어야 한다(객체·배열·원시·null). 함수·순환참조는 직렬화 에러로 실패가 된다.
  • 로그는 ctx.logger.* 또는 console.* — 데이터 채널이 아니라 진단·관측용이다. 결과는 항상 return으로 보낸다.
  • 실패는 throw — 던진 에러는 { message, name, stack } 형태로 잡 결과에 기록된다.

액션이 여러 개라면 windforce.jsonactions에 키를 더 넣고, SDK createApp({ actions })로 action별 핸들러를 묶어 ctx.action으로 분기한다. 같은 계약을 Python·Go로도 쓸 수 있다(저자 표면만 언어별로 갈리고 실행 모델은 동일). 전체 계약(input·trigger·결과·에러·상태·시크릿·멀티파일·manifest)은 스크립트 개발자 계약에 정리돼 있다.

파일을 커밋·푸시한 뒤, 위 2단계의 sync demo 1을 다시 실행하면 카탈로그가 새 커밋으로 갱신된다.

4. 잡 실행 · 결과 · 로그

공개 실행 주소는 하나의 형태로 통일돼 있다: /api/w/{workspace}/jobs/run/{app_key}/{action_key}. <token>을 2단계에서 발급받은 값으로 바꾼다.

# 실행 — 즉시 job_id를 돌려준다 (비동기 큐로 들어간다)
curl -X POST localhost:8080/api/w/demo/jobs/run/greet/hello \
  -H "Authorization: Bearer <token>" -d '{"name":"world"}'
# => {"job_id":"…"}

# 결과 — 잡이 끝나기 전이면 202를 돌려준다 (잠시 후 다시 호출)
curl localhost:8080/api/w/demo/jobs/<id>/result -H "Authorization: Bearer <token>"
# => {"status":"success","result":{"message":"hi world"}}

# 로그 — 잡이 stdout/stderr·ctx.logger로 남긴 출력
curl localhost:8080/api/w/demo/jobs/<id>/logs -H "Authorization: Bearer <token>"

<id>는 실행 응답의 job_id다. 실행은 비동기다 — run은 잡을 큐에 넣고 곧장 job_id를 돌려주고, 워커가 잡을 claim해 실행한 뒤 결과·로그를 영속 기록으로 남긴다. 프로덕션 배포에서는 워커가 2계층 샌드박스(gVisor + bubblewrap)로 격리 실행한다 — 샌드박싱 참고. 결과 조회는 완료 전이면 {"status":"pending"}과 HTTP 202를 받고, 성공하면 {"status":"success","result": …} 형태로 오며 result 안에 액션이 return한 값(여기서는 {"message":"hi world"})이 담긴다.

전체 흐름

flowchart TD
  author["저자: windforce.json + main(ctx)<br/>git repo에 커밋"]
  cli["bootstrap-workspace<br/>create-token · add-gitsource · sync"]
  catalog["app / action 카탈로그<br/>(commit·entrypoint·스키마 self-pin)"]
  run["POST /api/w/demo/jobs/run/greet/hello"]
  queue["PG 큐<br/>(비동기)"]
  worker["worker<br/>(잡 실행)"]
  result["GET .../jobs/{id}/result · /logs"]

  author --> cli
  cli --> catalog
  run --> queue
  catalog --> queue
  queue --> worker
  worker --> result

더 보기