name: deploy-server
description: Use this skill to provision or update a Welley server on a supported cloud (currently DigitalOcean) when the user asks to deploy a new instance or run a server update. Covers single-instance deployments and SSH-based updates, including interactive collection of missing credentials, SSH keys, domain, region, and sizing.
서버 배포
개요
이 스킬은 DigitalOcean에 단일 인스턴스 Welley 스택을 만들고, SSH로 업데이트도 실행해요. 에이전트는 빠진 입력값을 모으고, 정해진 출처에서 사용 가능한 인스턴스 사양을 가져오고, VM을 만들고, 호스트를 설정하고, wlycli deployment create로 배포한 뒤 결과를 확인해요.
명령 이름은 CLI와 똑같아야 해요.
wlycli deployment createwlycli deployment updatewlycli deployment delete
기본값 (DigitalOcean)
- 리전:
nyc3 - 이미지:
latest LTS available via API(images?type=distribution로 확인해요) - Droplet 이름:
wlsly-<adjective>-<surname>형식으로 무작위 생성해요 (예:wlsly-bright-hopper).references/digitalocean.md의 이름 생성 스니펫을 사용하세요. 관리형 DB를 만들 때도 같은 이름을 써요. - 플랜 목표: Basic / Regular (공유 CPU)
- 사양 목표: 2 vCPU / 4 GB RAM / 80 GB SSD / 4 TB 전송량 (월 약 $24)
기본값 변경 규칙
사용자가 기본값(리전, 이미지, Droplet 이름, 프로젝트, 사양) 중 하나라도 직접 지정하면 에이전트는 다음 순서를 따라야 해요.
- 지정한 내용을 그대로 다시 말해 줘요.
- 진행하기 전에 확인을 받아요.
대화형 입력 템플릿
빠진 것만 물어보세요. 헷갈리지 않도록 아래 질문을 그대로 사용해요. 여러 차례로 나눠서 물어보세요. 1차에서는 한 번에 최대 2개까지 질문해요. 2차에서는 기본값을 한꺼번에 받아들이는 “모든 기본값 수락” 선택지를 보여줄 수 있어요. 실행 환경이 선택지를 지원하면 질문마다 2~3개의 선택지(A/B/C)를 보여주세요. 꼭 필요한 경우가 아니면 자유 입력은 피해요.
1차 질문 (인증 정보와 접근 권한):
- “사용할 DigitalOcean API 토큰이 있나요, 아니면 토큰을 주실 때까지 기다릴까요?”
- “이 서버에 어떤 도메인을 쓸까요 (예:
super-puper.example.com)? 해당 도메인의 DNS를 직접 관리하나요?” - “사용할 SSH 공개 키가 이미 있나요, 아니면 제가 만들어서 업로드할까요?”
- “이 Droplet을 기존 DigitalOcean 프로젝트에 넣을까요? 그렇다면 프로젝트 이름이나 ID를 알려주세요.”
- “DNS 요구 사항:
<domain>과storage.<domain>(CDN을 쓰지 않는 경우)에 A/AAAA 레코드를 만들어야 해요. certbot을 실행하기 전에 만들어 주실 수 있나요?” - “이메일 DNS 요구 사항:
--smtp-from도메인에 SPF, DMARC, DKIM 레코드가 있어야 해요. 없으면 새 수신자에게 가는 초대 및 확인 이메일이 조용히 버려질 수 있어요. 이미 설정되어 있나요, 아니면 SMTP 제공업체에서 요구하는 레코드를 정리해 드릴까요?” - 사용자가 DO 토큰을 어디서 받는지 물으면 다음 링크를 알려주세요.
https://cloud.digitalocean.com/account/api/tokens
2차 질문 (사양, 위치, 배포 옵션):
- “기본 리전인
nyc3를 쓸까요, 아니면 다른 리전을 쓸까요?” - “기본 사양(2 vCPU / 2 GB)을 쓸까요, 아니면 다른 CPU/RAM 목표를 쓸까요?”
- “API에서 확인되는 최신 Ubuntu LTS를 쓸까요, 아니면 특정 Ubuntu 버전을 쓸까요?”
- “초기 설정 후 사용할 SSH 사용자는 무엇으로 할까요 (기본값:
wlsly)? 기본값을 그대로 써도 돼요.” - “관리형 Postgres DB를 쓸까요, 아니면 함께 제공되는 Postgres 컨테이너를 쓸까요?”
- 관리형 Postgres를 선택한 경우: “DB 리전과 사양은 어떻게 할까요?
nyc3와 1 vCPU / 2 GB를 기본으로 할 수 있어요.” - 관리형 Postgres를 선택한 경우: “제공업체에 표시된 월 가격으로 생성을 진행할까요?”
- “필수 SMTP 설정(host, user, password, from)을 알려주세요. OTEL은 선택 사항이니 필요할 때만 알려주세요.”
- “create에는 관리자 이메일이 필요해요. 지금 알려주세요 (플래그:
--email/-e). 알려주지 않으면 확인을 위해 두 번 입력받을게요.”
입력 수집을 빠르게 하려면 2차에서 “모든 기본값 수락” 선택지를 보여주세요.
답이 빠진 항목이 있으면 한두 가지만 콕 집어서 다시 물어보세요.
작업 순서 (DigitalOcean, 단일 인스턴스)
- 필수 입력값을 모아요 (빠진 것은 물어봐요).
- DigitalOcean API 토큰
- 프로젝트 이름 또는 ID (선택. 없거나 찾을 수 없으면 기본 프로젝트를 써요)
- 리전
- 인스턴스 사양 목표 (예: 4 vCPU / 16 GB RAM)
- 도메인 이름
- SSH 공개 키 (또는 새로 만들어 등록해도 된다는 확인)
- 첫 로그인과 비밀번호 복구에 쓸 관리자 이메일 (create에 필수,
--email/-e). 없으면 두 번 입력받아 일치하는지 확인해요. - 선택: 직접 준비한 TLS 인증서 또는 certbot
- DNS 접근 확인 (
<domain>과, 쓰는 경우storage.<domain>에 A/AAAA 레코드를 만들 수 있어야 해요)
- 인스턴스 유형을 가져와 사양을 골라요.
references/digitalocean.md의 사양 스니펫으로 목록을 가져와 CPU/RAM으로 걸러요.- 맞는 후보가 여러 개면 사용자에게 확인해요.
2b. Ubuntu 이미지 슬러그를 확인해요.
GET /v2/images?type=distribution&per_page=200을 호출해 Ubuntu만 걸러요.- 사용자가 특정 버전을 요청했다면 해당 슬러그가 있는지 확인하고 사용자에게 확인을 받아요.
- 그렇지 않으면 목록에서 최신 LTS 슬러그를 고르고, 생성하기 전에 사용자에게 확인을 받아요.
- Droplet을 만들어요.
- DigitalOcean API를 직접 사용해요 (doctl은 선택 사항이에요).
- 이미지: 사용자가 지정한 것을 우선하고, 없으면 API 목록에서 최신 LTS를 골라요.
- SSH 키 지문을 연결하거나, 키를 만들어 업로드해요.
- 모니터링을 켜요.
- 프로젝트 이름/ID를 받았지만 찾을 수 없으면 프로젝트 없이(기본 프로젝트) Droplet을 만들어요.
- 참고: 관리형 DB 생성은 Droplet 생성과 동시에 진행할 수 있어요.
병렬 처리 안내
에이전트는 서로 관련 없는 작업을 겹쳐서 실행해 전체 소요 시간을 줄여야 해요.
- 사용자 입력을 받는 중(2차)이라도 토큰, 리전, 사양, 이미지, SSH 키가 이미 확정되어 있으면 Droplet 생성을 시작해요.
- Droplet을 만드는 동안 관리형 Postgres 생성을 시작해요 (요청한 경우).
- Droplet이 시작되는 동안 남은 입력값(SMTP, OTEL, 관리자 이메일)을 계속 받아요.
- 어떤 단계에 꼭 필요한 입력이 빠져 있으면 그 단계만 멈추고, 병렬로 할 수 있는 다른 작업을 이어가요.
- DNS를 DigitalOcean에서 관리한다면 Droplet IP를 알게 되는 즉시 A/AAAA 레코드를 만들거나 수정해요 (전체 배포 단계를 기다리지 않아요).
- DNS / 도메인.
- DigitalOcean에서 DNS를 만들거나 수정할지, 외부 DNS를 그대로 둘지 물어봐요.
- DigitalOcean DNS를 쓴다면 Droplet IP를 가리키는 A/AAAA 레코드를 만들어요.
- SSH로 호스트를 준비해요.
- root가 아닌 사용자(예:
wlsly)를 만들고, sudo 권한을 주고, SSH authorized keys를 복사해요. - 다음 단계로 가기 전에 root에서 나와 root가 아닌 사용자로 다시 로그인하세요.
- Docker와 docker compose를 설치해요.
- AppStore에서 Welley CLI 바이너리를 받아 실행 가능하게 만들어요.
- 기본 URL:
https://appstore.allroads.social/v1/social.wellesley.wlycli/latest?develop=stable&platform=<os>/<arch> - OS와 아키텍처에 맞는
platform값을 고르세요.- Linux x86_64:
linux/amd64 - Linux arm64:
linux/arm64 - macOS x86_64:
darwin/amd64 - macOS arm64:
darwin/arm64 - Windows x86_64:
windows/amd64
- Linux x86_64:
- 예시 (Linux x86_64):
curl -fL 'https://appstore.allroads.social/v1/social.wellesley.wlycli/latest?develop=stable&platform=linux/amd64' -o wlycli chmod +x wlycli - 응답 헤더에 다음 항목이 들어 있어야 해요.
x-artifact-display-name: Welley CLIx-artifact-full-name: social.wellesley.wlyclix-artifact-kind: clix-artifact-platform: <os>/<arch>(예:linux/amd64)x-artifact-sha256: <sha256>x-artifact-version: <version>x-channel: develop
- 설치하기 전에 HEAD 요청으로 헤더를 먼저 확인하고, 아티팩트 이름, 종류, 플랫폼, 채널이 요청한 바이너리와 일치하는지 확인하는 것이 좋아요.
- 기본 URL:
- CLI 바이너리를 확인해요.
wlycli -v와wlycli deployment --help를 실행해 예상한 빌드 정보가 맞는지 봐요.
- Welley를 배포해요.
- 필수 옵션과 함께
wlycli deployment create를 실행해요. - 단일 인스턴스 기본값을 써요 (사용자가 요청하지 않으면 외부 Postgres는 쓰지 않아요).
- 관리형 Postgres를 요청받았다면 먼저 만들고(DigitalOcean DB), 반환된 host/port/db/user/password를
--postgres-*플래그에 사용해요. - API나 UI에서 DB 사용자 비밀번호를 알려주지 않으면,
wlycli를 실행하기 전에 DB 사용자 인증 정보를 초기화해서 새 자격 증명을 받으세요. - 비대화형 실행에는 반드시
--email/-e를 넘겨야 해요. 그렇지 않으면 명령이 실패해요. --base-dir은 상위 디렉터리를 가리켜요. 프로젝트 하위 디렉터리는 CLI가 자동으로 만들어요.- 프로젝트 디렉터리나 컨테이너가 이미 있으면 CLI가 실행을 거부해요. 다시 시도하기 전에 정리해야 해요.
- 인증서 (필수).
- 기본값: Certbot standalone을 사용해요 (비대화형).
- certbot을 실행하기 전에 사용자가 도메인(쓰는 경우
storage.<domain>포함)의 A/AAAA 레코드를 Droplet IP로 지정해 두어야 해요. - CLI는
--public-ip(감지된 IP를 덮어써요)를 지원하고, 검사를 꼭 건너뛰어야 한다면--skip-dns-preflight도 지원해요. - DNS가 설정되어 있다고 절대 가정하지 마세요. 물어보고 분명한 확인을 받을 때까지 기다리세요.
- 사용자가 명시적으로 요청하지 않으면
--skip-dns-preflight를 절대 쓰지 마세요. - CDN을 설정하지 않았다면 certbot 인증서 요청에
storage.<domain>을 꼭 포함해야 해요. - 자체 서명 인증서는 사용자가 명시적으로 요청할 때만 쓰고,
--self-signed-certs를 넘겨요 (Go로 자체 서명 인증서를 만들어요). - 사용자에게 DNS 설정 방법을 안내하고 확인을 기다리세요.
- “
<domain>이 Droplet IP로 연결되도록 A/AAAA 레코드를 만들어 주세요.” - “
storage.<domain>을 쓴다면 그것도 A/AAAA 레코드를 만들어 주세요.” - “DNS가 반영되면 알려주세요. 그러면 이어서 진행할게요. 확인해 달라고 요청하시지 않으면 DNS 반영 여부를 제가 볼 수 없어요.”
- 에이전트가
ping -c 1 <domain>으로 DNS 반영을 직접 확인하고 IP를 Droplet과 비교해도 돼요. 일치하면 진행해요.
7b. 이메일 DNS (SPF / DMARC / DKIM).
- SPF와 DMARC가 없는 도메인에서 보낸 메일은, SMTP 제공업체에서는 전달됐다고 나와도 처음 받는 사람의 Gmail이나 Outlook에서 버려지는 경우가 많아요.
--smtp-from도메인에 SPF 레코드가 없거나,_dmarc.<domain>또는 상위 도메인에 DMARC 레코드가 없으면deployment create가Warning:을 출력해요. 배포를 막지는 않아요. 이 경고를 절대 무시하지 말고, 사용자에게 보여주고 추가할 레코드를 알려주세요.<mail-domain>TXTv=spf1 include:<provider> ~all(정확한 include 값은 SMTP 제공업체에서 알려줘요. 기존 SPF 레코드가 있으면 거기에 합치고, 두 번째 레코드를 추가하지 마세요)_dmarc.<mail-domain>TXTv=DMARC1; p=none; rua=mailto:dmarc@<mail-domain>- DKIM: 도메인을 인증할 때 SMTP 제공업체가 만들어 주는
<selector>._domainkey.<mail-domain>레코드
dig +short TXT <mail-domain>과dig +short TXT _dmarc.<mail-domain>으로 확인해요.- CLI는 DKIM을 확인할 수 없어요 (selector가 제공업체마다 달라요).
create가 경고를 출력하지 않았더라도 SMTP 제공업체에서 DKIM이 인증되었는지 꼭 사용자에게 확인하세요. Amazon SES 같은 제공업체는 SPF를 제공업체 자체의 envelope 도메인 기준으로 검사하기 때문에, DMARC를 통과시킬 수 있는 것은 DKIM뿐이에요. SES에서는 Verified identities에서<mail-domain>을 열었을 때 DKIM 항목이 Successful로 표시되어야 해요.
- 확인하고 결과를 알려요.
docker ps와 서비스 상태 확인 엔드포인트를 확인해요.- 최종 접속 정보와 다음 단계를 알려주세요.
SSH 업데이트 과정
사용자가 “서버 업데이트”를 요청하면 다음 순서로 진행해요.
- 대상 호스트/IP와 SSH 사용자/키를 물어봐요.
wlycli deployment update --domain <domain> [--force-restart]를 실행해요.- 컨테이너와 Nginx 상태를 확인해요.
참고 자료
- DigitalOcean API 스니펫과 예시:
references/digitalocean.md
비밀 값과 플래그
비밀 값에는 다음 플래그 이름을 사용해요.
- SMTP host:
--smtp-host - SMTP user:
--smtp-user - SMTP password:
--smtp-password - SMTP from:
--smtp-from - OTEL endpoint:
--otel-endpoint - OTEL headers:
--otel-headers
최소 create 명령
필수 플래그:
--domain--email--smtp-host--smtp-user--smtp-password--smtp-from
최소 예시:
wlycli deployment create \
--domain example.com \
--email admin@example.com \
--smtp-host smtp.example.com \
--smtp-user user \
--smtp-password pass \
--smtp-from noreply@example.com
관리형 Postgres 기본값 (DigitalOcean과 CLI 비교)
DigitalOcean 관리형 Postgres를 쓸 때는 CLI 기본값에 기대지 말고 --postgres-* 플래그를 모두 직접 지정하세요.
- DO의 일반적인 기본값:
- 포트:
25060 - 데이터베이스:
defaultdb - 사용자:
doadmin
- 포트:
- CLI 기본값 (플래그를 생략했을 때):
- 포트:
5432 - 데이터베이스:
wlsly - 사용자:
wlsly
- 포트:
CLI 사용 참고
- 버전 확인:
wlycli -v또는wlycli --version(플래그예요).version하위 명령은 없어요. --base-dir은 기본이 아닌 위치를 쓰고 싶을 때만 사용하세요 (기본값은~/.welley). 권한이 있으면 CLI가 디렉터리를 만들어요.- 비대화형 SSH 실행에는
--email과 필요한 플래그를 모두 넣어서 질문이 나오지 않게 하세요. - 사용자가 서버에 CLI 바이너리를 직접 두었다면(예:
~/wcli) 그대로 사용하세요. 요청이 없으면 옮기지 마세요. - DO API를 호출할 때는
references/digitalocean.md의 스니펫을 그대로 복사하세요. Python 파싱 부분을 고쳐 쓰거나 바꾸거나 즉흥적으로 만들지 마세요. 이스케이프나 따옴표 같은 작은 변경도 셸에 따라 깨질 수 있어요. - SSH 원격 스크립트: 항상
ssh host 'bash -s' << 'EOF'(따옴표를 붙인 heredoc)를 사용하세요. 큰따옴표를 쓰는ssh host "..."는 쓰지 마세요. zsh가 원격 호스트로 보내기 전에$(...)와[...]를 로컬에서 먼저 해석해 버려요.
실패 후 복구
deployment create가 실패해서 다시 시도해야 한다면:
wlycli deployment delete --domain <domain> --yes-please-delete-all-the-data로 정리하세요.- delete는 root 소유 파일을 컨테이너를 이용해 정리하려고 시도해요. 그래도 실패하면 상승된 권한으로
--base-dir아래의 프로젝트 디렉터리를 지우고 다시 실행하세요.
사전 80번 포트 테스트 (선택)
certbot이 실패하면 다시 실행하기 전에 80번 포트에 접근할 수 있는지 먼저 테스트하세요.
docker run --rm -d -p 80:80 --name test-http nginx:alpine
curl -s http://<domain>:80
docker rm -f test-http