워크플로우
이 문서는 워크플로우 YAML의 모든 필드를 다뤄요.
입문자라면 워크플로우 생성부터 보시는 게 좋아요.
파일 규칙
- 위치: Agent의 워크플로우 디렉토리 (기본
./workflows/,DEPLITE_WORKFLOWS_DIR로 오버라이드 가능) - 확장자:
.yaml또는.yml - 파일이 추가·수정·삭제되면 Agent가 파일시스템 이벤트로 즉시 감지해서 반영해요
최상위 필드
| 필드 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
name | string | ✓ | — | 워크플로우 식별자. 영문 대소문자·숫자·_·.·-만 허용, 1~128자 |
steps | object[] | ✓ | — | 최소 1개 |
timeout-minutes | int | 전체 워크플로우 timeout (지정 시 step 전체에 적용) | ||
env | object | {} | 모든 step에 적용되는 환경변수 | |
secrets | (string | object)[] | [] | Agent 프로세스의 DEPLITE_SECRET_<KEY> 환경변수에서 가져와 마스킹할 시크릿 목록 (시크릿 참고) | |
outputs | object[] | [] | step이 캡처한 값을 워크플로우 결과로 노출 (출력 참고) |
Step 필드
| 필드 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
name | string | ✓ | — | 워크플로우 안에서 고유해야 해요 |
run | string | ✓ | — | 실행할 셸 명령 (multi-line 가능) |
id | string | — | step 식별자. 형식 ^[a-zA-Z][a-zA-Z0-9_-]{0,63}$, 워크플로우 안에서 유일해야 해요. 출력값 캡처와 시크릿 scope 지정에 쓰여요 | |
verbose | bool | false | debug 실행 대상 step 표시 (아래 참고) | |
timeout-minutes | int | (워크플로우 값 상속) | step별 timeout | |
working-directory | string | (Agent 작업 폴더) | cd 후 실행 | |
env | object | {} | step별 환경변수 | |
shell | string | (Agent 기본 셸) | 이 step만 다른 셸로 실행할 때 사용 | |
continue-on-error | bool | false | 실패해도 다음 step 진행 |
시크릿 값은 stdout/stderr에 노출되어도 ***로 자동 마스킹돼요.
verbose: true 동작
step에 verbose: true를 선언하면, 트리거를 debug: true로 호출했을 때 해당 step의 원본 stdout/stderr가 그대로 서버에 전송돼요.
워크플로우 안의 어느 step도 verbose: true로 선언되지 않으면 debug: true 호출 자체가 거절돼요.
환경변수 우선순위
낮은 우선순위부터 높은 우선순위 순서로 적용돼요.
- Agent 프로세스 환경변수 (호스트)
DEPLITE_WORKDIR(Agent가 자동 주입하는 step 작업 디렉토리),TMPDIR·TMP- 워크플로우
env DEPLITE_PARAM_<KEY>(트리거의params에서 자동 주입)DEPLITE_SECRET_<KEY>(워크플로우secrets목록의 시크릿)- Step
env
같은 키가 여러 곳에 있으면 아래쪽이 이겨요.
step이 실행되는 머신(호스트 또는 컨테이너)과 PATH 처리 방식은 스텝 실행 환경에서 다뤄요.
트리거 주입 변수
| 환경변수 | 의미 |
|---|---|
DEPLITE_PARAM_<KEY> | 트리거 호출 시 params.<key> 값 (대문자로 변환) |
DEPLITE_SECRET_<KEY> | 워크플로우 secrets에 선언된 키. Agent 환경변수 DEPLITE_SECRET_<KEY>에서 가져옴 |
DEPLITE_WORKDIR | step별로 새로 생성되는 임시 작업 디렉토리 |
TMPDIR, TMP | DEPLITE_WORKDIR과 같은 값 |
DEPLITE_OUTPUT | step에 id가 있을 때만 주입. 출력 캡처용 파일 경로 (출력 참고) |
DEPLITE_OUTPUT_DIR | 워크플로우 전역 출력 디렉토리. 여기에 result.json을 쓰면 결과로 실려요 |
트리거의 ref·debug·jobId는 환경변수로 자동 주입되지 않아요.
워크플로우에서 받고 싶다면 트리거의 params에 명시적으로 포함해서 호출하면 DEPLITE_PARAM_REF 같은 형태로 받을 수 있어요.
시크릿
secrets: 배열의 항목은 이름만 적는 문자열과 객체를 섞어 쓸 수 있어요.
secrets:
- DATABASE_URL # 문자열 형태는 required: true가 적용돼요
- name: DEPLOY_KEY
required: true
scope: [deploy] # id가 deploy인 step에만 주입돼요
- name: SLACK_WEBHOOK
required: false| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
name | string | ✓ | UPPER_SNAKE_CASE. 형식 ^[A-Z][A-Z0-9_]{0,63}$ |
required | bool | 값이 바인딩되지 않았을 때 job을 실패시킬지 | |
description | string | 설명 | |
scope | string[] | 이 시크릿을 볼 수 있는 step id 목록 |
여기 나열된 이름은 stdout에서 자동 마스킹되고, 어떤 경우에도 서버로 전송되지 않아요.
시크릿 값은 Agent 머신의 환경변수에 DEPLITE_SECRET_DATABASE_URL·DEPLITE_SECRET_API_KEY 형태로 주입해주세요.
scope로 주입 범위 좁히기
scope를 생략하거나 빈 배열로 두면 모든 step에 주입돼요 (기본 동작)- 지정하면 목록에 있는
id를 가진 step에만 주입돼요 id가 없는 step은scope가 지정된 시크릿을 항상 받지 못해요- 주입 형태는 동일하게
DEPLITE_SECRET_<이름>환경변수예요 required검사는scope와 무관하게 워크플로우 단위로 먼저 이뤄져요. 값만 바인딩되어 있으면 scope로 가려진 step이 있어도 통과해요- 마스킹도
scope와 무관하게 바인딩된 모든 시크릿 값에 적용돼요 scope는 서버로 전송되지 않는 Agent 로컬 정책이에요
scope에 적은 step id가 실제로 존재하는지는 검증하지 않아요.
이름을 잘못 적으면 에러 없이 어떤 step에도 주입되지 않고, step은 빈 환경변수로 실행돼요.
출력 (output)
Job의 output을 만드는 방법은 두 가지고, 둘 다 stdout이 아니라 파일이에요.
| 방법 | 주입 변수 | step id | 쓰임새 |
|---|---|---|---|
| 선언형 캡처 | DEPLITE_OUTPUT (step별 파일) | 필요 | key=value로 값을 캡처하고 outputs:에서 타입을 지정해 노출 |
| 자유형 JSON | DEPLITE_OUTPUT_DIR (워크플로우 전역 디렉토리) | 불필요 | result.json에 임의 구조의 JSON. 파일을 결과로 첨부할 수 있음 |
stdout에 JSON을 출력해도 output으로 잡히지 않아요.
stdout·stderr는 로그 스트림(raw)으로만 흘러요.
변수 이름이 DEPLITE_OUTPUT(단수, step별 파일)과 DEPLITE_OUTPUT_DIR(워크플로우 전역 디렉토리)로 한 끗 차이니 헷갈리지 않게 확인해주세요.
Job output은 트리거를 동기 응답 모드로 호출했을 때 호출자에게 반환돼요.
1. outputs:와 $DEPLITE_OUTPUT
step에 id를 붙이면 그 step에만 DEPLITE_OUTPUT 환경변수가 주입돼요.
값은 해당 작업 전용 임시 디렉토리(DEPLITE_WORKDIR) 안의 파일 경로예요.
이 파일에 값을 적어두면 Agent가 step 종료 후 읽어서 캡처해요.
steps:
- id: build
name: 빌드
run: |
echo "commit=$(git rev-parse HEAD)" >> "$DEPLITE_OUTPUT"
echo "size=42" >> "$DEPLITE_OUTPUT"
outputs:
- name: commit
type: string
from: build.commit
- name: size
type: number
from: build.size파일 포맷
- 한 줄에
key=value하나예요 key는^[a-zA-Z][a-zA-Z0-9_-]{0,63}$형식이에요- 첫
=를 기준으로 자르기 때문에 값 안에=가 들어가도 괜찮아요 =가 없는 줄과 형식에 맞지 않는 key의 줄은 무시돼요 (실패로 처리되지 않아요)- 값은 시크릿 마스킹을 거친 뒤 캡처되고, 파일은 step이 끝나면 읽힌 뒤 삭제돼요
멀티라인 값 문법은 없어요.
GitHub Actions의 구분자(<<EOF) 방식은 지원하지 않으니, 여러 줄 값이 필요하면 DEPLITE_WORKDIR 아래에 파일로 남겨주세요.
outputs로 꺼내기
캡처된 값은 워크플로우 최상위 outputs:를 통해서만 노출돼요.
outputs:를 선언하지 않으면 캡처한 값은 어디에도 실리지 않아요.
| 필드 | 설명 |
|---|---|
name | 워크플로우 결과에 실릴 이름 |
type | string · number · boolean |
from | <step-id>.<key> 형식. 형식이 틀리거나 없는 step id를 가리키면 검증 에러 |
캡처된 값은 후속 step의 환경변수로 들어가지 않아요.
GitHub Actions의 steps.<id>.outputs.<key>처럼 다음 step에서 바로 참조할 수 없고, 워크플로우 outputs:에만 투영돼요.
step 사이에 값을 넘기려면 DEPLITE_WORKDIR 아래에 파일을 직접 쓰고 읽어주세요.
2. $DEPLITE_OUTPUT_DIR/result.json
DEPLITE_OUTPUT_DIR은 워크플로우 전체에 주입되는 디렉토리 경로예요.
step id 없이도 쓸 수 있고, 이 디렉토리에 result.json을 쓰면 Agent가 파싱해서 그대로 결과에 실어요.
파일이 없으면 조용히 넘어가요.
- name: report
run: |
cat > "$DEPLITE_OUTPUT_DIR/result.json" <<'JSON'
{"deployedRef":"main-7f3a9b","url":"https://app.example.com"}
JSONresult.json의 최상위는 반드시 JSON 객체여야 해요.
배열이나 스칼라면 결과 병합 자체를 건너뛰어요.
$file 마커로 파일 첨부하기
result.json 트리 안에 {"$file": "<상대 경로>"} 마커를 두면, 해당 파일이 업로드된 뒤 그 자리가 FileRef 객체로 치환돼요.
파일을 결과로 돌려주는 방법은 이것뿐이에요.
- name: build-and-attach
run: |
./build.sh > "$DEPLITE_OUTPUT_DIR/app.apk"
cat > "$DEPLITE_OUTPUT_DIR/result.json" <<'JSON'
{"version":"1.4.0","artifact":{"$file":"app.apk"}}
JSON마커 인식 조건은 다음과 같아요.
- 객체의 키가 정확히 하나이고 그 키가
$file일 때만 치환돼요. 다른 키가 하나라도 같이 있으면 일반 객체로 그대로 실려요 - 경로는
DEPLITE_OUTPUT_DIR기준 상대 경로이고, 대상은 정규 파일이어야 해요 (디렉토리를 가리키면 실패해요) - 배열이나 중첩 객체 안의 마커도 재귀적으로 치환돼요
{ "artifact": { "$file": "report.tar.gz" } } // 치환됨
{ "artifact": { "$file": "report.tar.gz", "note": "x" } } // 치환 안 됨치환된 FileRef 객체는 이런 모양이에요.
{
"__type": "FileRef",
"id": "9f1c…",
"filename": "report.tar.gz",
"contentType": "application/octet-stream",
"size": 20481
}| 필드 | 타입 | 값 |
|---|---|---|
__type | string | 항상 "FileRef" |
id | string | 파일 id. 다운로드에 쓰는 값 |
filename | string | 마커에 적은 경로의 basename만 |
contentType | string | 항상 application/octet-stream |
size | number | 바이트 |
내려받을 때는 id로 GET /storage/files/:id/download-url을 호출해주세요.
출력 FileRef에는 downloadUrl·expiresAt이 들어 있지 않아서, API를 거치는 것이 유일한 경로예요.
업로드된 파일은 persistent로 저장되어 job이 끝나도 자동 삭제되지 않아요.
contentType은 실제 확장자와 무관하게 항상 application/octet-stream이에요.
이 값으로 파일 종류를 분기하지 말아주세요.
App Deploy(OTA)의 deploy 이벤트에 실리는 입력 방향 FileRef는 downloadUrl을 갖지만, 여기서 다루는 출력 방향 FileRef는 URL이 없어요.
업로드에 실패하면
업로드가 실패해도 job은 실패하지 않아요.
경고 로그만 남고 job 상태는 그대로예요.
업로드에는 job당 10분 타임아웃이 걸려 있고, 초과해도 같은 방식으로 경고만 남기고 진행돼요.
- 마커 하나라도 업로드에 실패하면
result.json트리 전체가output에서 빠져요. 성공한 마커만 골라 담는 부분 성공은 없어요 - 마커가 원래 모양으로 남지도 않아요. 트리 자체가 병합 대상에서 제외돼요
outputs:로 캡처한 값은 영향을 받지 않고 그대로output에 남아요
그래서 호출자 입장에서는 “output은 왔는데 파일 필드만 통째로 없는” 모습이 돼요.
파일이 반드시 필요한 워크플로우라면, 다운스트림에서 해당 필드가 실제로 있는지 확인하도록 만들어두시는 걸 권해드려요.
두 결과의 병합
- 두 경로의 결과는 하나의 Job
output객체로 병합돼요. 별도 필드로 나뉘지 않아요 - 키가 겹치면
result.json쪽이 이겨요 (outputs:결과 위에 덮어써요) - 결과가 비어 있으면
output필드는 아예 전송되지 않아요
검증 규칙
name: 영문 대소문자·숫자·_·.·-만 허용, 1~128자steps: 1개 이상- 각
step.name: 비어있지 않고, 같은 워크플로우 안에서 중복되지 않음 - 각
step.run: 비어있지 않음 - 각
step.id(선택):^[a-zA-Z][a-zA-Z0-9_-]{0,63}$형식이고, 같은 워크플로우 안에서 중복되지 않음 - 각
outputs[].from:<step-id>.<key>형식이고, 실제로 존재하는 stepid를 가리킴
규칙을 어기면 Agent 시작 시 또는 워크플로우 파일이 다시 스캔될 때 에러 로그가 남고, 해당 파일은 서버 카탈로그에서 제외돼요.
실전 레시피 모음
1) DB 야간 백업
PostgreSQL 표준 환경변수(PGHOST·PGUSER·PGPASSWORD·PGDATABASE)를 step에 주입하면 psql 계열 도구(psql·pg_dump 등)가 별도 인자 없이 자동 인식해요.
name: backup-pg
timeout-minutes: 60
secrets:
- PGPASSWORD
- AWS_SECRET_ACCESS_KEY
env:
PGHOST: db.internal
PGPORT: "5432"
PGUSER: backup
PGDATABASE: prod
steps:
- name: connectivity-check
run: psql -c "SELECT 1" > /dev/null
env:
PGPASSWORD: $DEPLITE_SECRET_PGPASSWORD
timeout-minutes: 1
- name: dump
run: pg_dump --no-owner --clean | gzip > /tmp/backup.sql.gz
env:
PGPASSWORD: $DEPLITE_SECRET_PGPASSWORD
timeout-minutes: 30
- name: upload
run: |
aws s3 cp /tmp/backup.sql.gz \
s3://backups/pg/$(date +%F).sql.gz
timeout-minutes: 15
- name: cleanup
run: rm -f /tmp/backup.sql.gz
continue-on-error: true2) Docker 이미지 빌드·푸시·배포
name: deploy-myapp
timeout-minutes: 25
env:
IMAGE: registry.example.com/myapp
secrets:
- REGISTRY_PASSWORD
steps:
- name: build
run: docker build -t "$IMAGE:$DEPLITE_PARAM_REF" .
timeout-minutes: 12
- name: push
run: |
echo "$DEPLITE_SECRET_REGISTRY_PASSWORD" | docker login -u deploy --password-stdin registry.example.com
docker push "$IMAGE:$DEPLITE_PARAM_REF"
timeout-minutes: 8
- name: rollout
run: |
kubectl set image deployment/myapp myapp="$IMAGE:$DEPLITE_PARAM_REF"
kubectl rollout status deployment/myapp --timeout=5m
timeout-minutes: 6
- id: report
name: report
run: |
DEP_URL=$(kubectl get ingress myapp -o jsonpath='{.spec.rules[0].host}')
echo "deployedRef=$DEPLITE_PARAM_REF" >> "$DEPLITE_OUTPUT"
echo "url=https://$DEP_URL" >> "$DEPLITE_OUTPUT"
outputs:
- name: deployedRef
type: string
from: report.deployedRef
- name: url
type: string
from: report.url트리거 호출 시 params: { "ref": "main-7f3a9b" }로 넘기면 워크플로우에서 $DEPLITE_PARAM_REF로 받을 수 있어요.
3) 외부 API → 데이터 웨어하우스 ETL
name: etl-orders
timeout-minutes: 45
secrets:
- SHOPIFY_TOKEN
- SNOWFLAKE_PWD
env:
WINDOW_HOURS: "24"
steps:
- name: extract
run: |
curl -H "X-Shopify-Access-Token: $DEPLITE_SECRET_SHOPIFY_TOKEN" \
"https://store.myshopify.com/admin/api/2024-01/orders.json?updated_at_min=$(date -d '24 hours ago' -Iseconds)" \
> /tmp/orders.json
timeout-minutes: 15
- name: transform
run: python3 /opt/etl/transform.py /tmp/orders.json /tmp/orders.parquet
timeout-minutes: 10
- name: load
run: python3 /opt/etl/load_snowflake.py /tmp/orders.parquet
env:
SNOWFLAKE_USER: deplite
SNOWFLAKE_ACCOUNT: ab12345.us-east-1
timeout-minutes: 204) 헬스체크 + 자동 복구
name: healthcheck
timeout-minutes: 5
steps:
- name: probe
run: |
if ! curl -fsS https://app.example.com/health > /dev/null; then
echo "UNHEALTHY"
exit 1
fi
echo "OK"
- name: restart-if-failed
run: kubectl rollout restart deployment/myapp
continue-on-error: true서버 측 트리거의 쿨다운·동시 실행 제한은 제한과 정책에서 다뤄요.
5) 디버그 환경 임시 부팅 + verbose
debug 모드로 호출할 step에 verbose: true를 붙여 두면, 트리거를 debug: true로 부를 때 원본 로그가 그대로 전송돼요.
name: spin-up-debug
timeout-minutes: 15
steps:
- id: launch
name: launch
verbose: true
run: |
ID=$(docker run -d --rm -p 0:3000 myapp:debug)
HOST_PORT=$(docker port "$ID" 3000 | awk -F: '{print $2}')
echo "containerId=$ID" >> "$DEPLITE_OUTPUT"
echo "url=http://debug.internal:$HOST_PORT" >> "$DEPLITE_OUTPUT"
outputs:
- name: containerId
type: string
from: launch.containerId
- name: url
type: string
from: launch.urlWebhook을 동기 응답 모드로 호출하면 응답의 output.url을 그대로 Slack에 던질 수 있어요.