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.
Deploy Server
Overview
This skill provisions a single-instance Welley stack on DigitalOcean and can run updates over SSH. It guides the agent to gather missing inputs, fetch available instance specs from a defined source, create the VM, configure the host, deploy with wlycli deployment create, and validate.
Command names must match the CLI:
wlycli deployment createwlycli deployment updatewlycli deployment delete
Defaults (DigitalOcean)
- Region:
nyc3 - Image:
latest LTS available via API(resolve viaimages?type=distribution) - Droplet name: randomly generated as
wlsly-<adjective>-<surname>(e.g.,wlsly-bright-hopper). Use the name generator snippet fromreferences/digitalocean.md. Use the same name for the managed DB if created. - Plan target: Basic / Regular (Shared CPU)
- Size target: 2 vCPU / 4 GB RAM / 80 GB SSD / 4 TB transfer (~$24/mo)
Default Override Rule
If the user specifies any of the defaults (region, image, droplet name, project, size), the agent must:
- Echo the override back verbatim.
- Ask for confirmation before proceeding.
Interactive Input Template
Ask only what’s missing. Use these exact prompts to reduce ambiguity. Ask in iterative rounds. Round 1: max 2 questions per turn. Round 2 may present a bundled “accept all defaults” option. If the runtime supports choices, present 2–3 options per question (A/B/C). Avoid free-form unless necessary.
Round 1 questions (identity + access):
- “Do you have a DigitalOcean API token to use, or should I wait for you to provide one?”
- “Which domain should this server use (e.g.,
super-puper.example.com)? Do you control its DNS?” - “Do you already have an SSH public key to use, or should I create and upload one?”
- “Do you want to assign this droplet to an existing DigitalOcean project? If yes, provide the project name or ID.”
- “DNS requirement: you must create A/AAAA records for
<domain>andstorage.<domain>(if no CDN). Can you do that before certbot runs?” - “Email DNS requirement: the
--smtp-fromdomain needs SPF, DMARC and DKIM records, or invitation and confirmation emails to new recipients may be silently dropped. Are they in place, or should I list the records your SMTP provider needs?” - If the user asks where to get a DO token, provide this link:
https://cloud.digitalocean.com/account/api/tokens
Round 2 questions (sizing + location + deploy options):
- “Do you want the default region
nyc3, or a different region?” - “Do you want the default size (2 vCPU / 2 GB), or a different CPU/RAM target?”
- “Should I use the latest Ubuntu LTS available in the API, or a specific Ubuntu version?”
- “Which SSH user should we use after bootstrap (default:
wlsly)? You can keep the default.” - “Do you want a managed Postgres DB, or use the bundled Postgres container?”
- If managed Postgres is chosen: “Which DB region and size should I use? I can default to
nyc3and 1 vCPU / 2 GB.” - If managed Postgres is chosen: “Do you want me to proceed with creation at the provider’s listed monthly price?”
- “Please provide required SMTP settings (host, user, password, from). OTEL is optional — provide it only if needed.”
- “Admin email is required for create. Provide it now (flag:
--email/-e). If not provided, I will prompt twice to confirm it.”
Offer an “accept all defaults” option in Round 2 to speed up input collection.
If any answer is missing, ask a focused follow-up (one or two questions).
Workflow (DigitalOcean, single instance)
- Collect required inputs (ask if missing).
- DigitalOcean API token
- Project name or ID (optional; if missing or not found, use default project)
- Region
- Instance size target (e.g., 4 vCPU / 16 GB RAM)
- Domain name
- SSH public key (or confirmation to create and register one)
- Admin email for first login and password recovery (required for create;
--email/-e). If missing, prompt twice and confirm match. - Optional: custom TLS certs vs certbot
- DNS access confirmation (must be able to create A/AAAA records for
<domain>andstorage.<domain>if used)
- Fetch instance types and pick a size.
- Use the sizes snippet from
references/digitalocean.mdto list and filter by CPU/RAM. - Confirm with the user if multiple matches.
2b. Resolve Ubuntu image slug.
- Call
GET /v2/images?type=distribution&per_page=200and filter for Ubuntu. - If the user asked for a specific version, verify its slug exists and confirm.
- Otherwise pick the latest LTS slug from the list and confirm with the user before creation.
- Create the droplet.
- Use the DigitalOcean API directly (doctl is optional).
- Image: prefer user-specified; otherwise pick the latest LTS from the API list.
- Attach the SSH key fingerprint or create and upload a key.
- Enable monitoring.
- If project name/ID was provided but not found, create the droplet without a project (default project).
- Note: Managed DB creation can run in parallel with droplet creation.
Parallelization Guidance
The agent should overlap independent tasks to reduce wall‑clock time:
- While gathering user inputs (Round 2), start droplet creation if token, region, size, image, and SSH key are already confirmed.
- While droplet is provisioning, start managed Postgres creation (if requested).
- While droplet is starting, continue collecting remaining inputs (SMTP, OTEL, admin email).
- If any critical input is missing for a step, pause that step and continue with other parallelizable tasks.
- If DNS is managed in DigitalOcean, create/update A/AAAA records as soon as the droplet IP is available (do not wait for the full deployment step).
- DNS / domain.
- Ask whether to create or update DNS in DigitalOcean or leave external DNS.
- If using DigitalOcean DNS, create A/AAAA records to the droplet IP.
- Provision the host via SSH.
- Create a non-root user (e.g.,
wlsly), grant sudo, and copy SSH authorized keys. - Exit root and re-login as the non-root user before continuing.
- Install Docker + docker compose.
- Fetch the Welley CLI binary from AppStore and make it executable:
- Base URL:
https://appstore.allroads.social/v1/social.wellesley.wlycli/latest?develop=stable&platform=<os>/<arch> - Choose the correct
platformvalue by OS/arch:- 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:
- Example (Linux x86_64):
curl -fL 'https://appstore.allroads.social/v1/social.wellesley.wlycli/latest?develop=stable&platform=linux/amd64' -o wlycli chmod +x wlycli - Expected response headers include:
x-artifact-display-name: Welley CLIx-artifact-full-name: social.wellesley.wlyclix-artifact-kind: clix-artifact-platform: <os>/<arch>such aslinux/amd64x-artifact-sha256: <sha256>x-artifact-version: <version>x-channel: develop
- Before installing, prefer checking headers with a HEAD request and verify that artifact name, kind, platform, and channel match the requested binary.
- Base URL:
- Verify CLI binary: run
wlycli -vandwlycli deployment --helpto confirm expected build info.
- Deploy Welley.
- Run
wlycli deployment createwith the required options. - Use single-instance defaults (no external Postgres unless user asks).
- If managed Postgres is requested, create it first (DigitalOcean DB) and then use the returned host/port/db/user/password in
--postgres-*flags. - If the DB user password is not returned by the API/UI, reset the DB user auth to obtain fresh credentials before running
wlycli. - Non-interactive runs must pass
--email/-eor the command will fail. --base-dirpoints to the parent directory; the CLI creates the project subdirectory automatically.- The CLI refuses to run if the project directory or containers already exist; cleanup is required before retrying.
- Certificates (mandatory).
- Default: use Certbot standalone (non-interactive).
- User must point DNS A/AAAA records for the domain (and
storage.<domain>if used) to the droplet IP before certbot runs. - CLI supports
--public-ip(override detected IP) and--skip-dns-preflightif you must bypass the check. - Never assume DNS is configured. Ask and wait for explicit confirmation.
- Never use
--skip-dns-preflightunless the user explicitly requests it. - If CDN is not configured, certbot must include
storage.<domain>in the certificate request. - Use self-signed certificates only if the user explicitly asks and pass
--self-signed-certs(generates self-signed certs in Go). - Provide the user with DNS setup instructions and wait for confirmation:
- “Create A/AAAA records so
<domain>resolves to the droplet IP.” - “If
storage.<domain>is used, create an A/AAAA record for it too.” - “Once DNS propagates, tell me and I’ll continue. I cannot observe propagation unless you ask me to verify resolution.”
- The agent may verify DNS propagation itself using
ping -c 1 <domain>and compare the IP to the droplet. If it matches, proceed.
7b. Email DNS (SPF / DMARC / DKIM).
- Mail from a domain without SPF and DMARC is often dropped by Gmail and Outlook for first-time recipients, even though the SMTP provider reports it as delivered.
deployment createprints aWarning:when the--smtp-fromdomain has no SPF record, or no DMARC record at_dmarc.<domain>or a parent domain. It does not block the deploy. Never ignore the warning: show it to the user and give them the records to add:<mail-domain>TXTv=spf1 include:<provider> ~all(the exact include comes from the SMTP provider; merge into an existing SPF record, never add a second one)_dmarc.<mail-domain>TXTv=DMARC1; p=none; rua=mailto:dmarc@<mail-domain>- DKIM: the
<selector>._domainkey.<mail-domain>records the SMTP provider generates when the domain is verified
- Check with
dig +short TXT <mail-domain>anddig +short TXT _dmarc.<mail-domain>. - The CLI cannot check DKIM (the selector is provider-specific). Always ask the user to confirm DKIM is verified at the SMTP provider, even when
createprinted no warning. With Amazon SES and similar providers, SPF is checked for the provider's own envelope domain, so only DKIM can make DMARC pass. In SES: Verified identities →<mail-domain>→ DKIM must read Successful.
- Validate and report.
- Check
docker psand service health endpoints. - Provide final connection details and next steps.
SSH Update Flow
When user asks for “update server”:
- Ask for target host/IP and SSH user/key.
- Run
wlycli deployment update --domain <domain> [--force-restart]. - Verify containers and Nginx status.
Resources
- DigitalOcean API snippets and examples:
references/digitalocean.md.
Secrets to Flags
Use these flag names for secrets:
- SMTP host:
--smtp-host - SMTP user:
--smtp-user - SMTP password:
--smtp-password - SMTP from:
--smtp-from - OTEL endpoint:
--otel-endpoint - OTEL headers:
--otel-headers
Minimal Create Command
Required flags:
--domain--email--smtp-host--smtp-user--smtp-password--smtp-from
Minimal example:
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
Managed Postgres Defaults (DigitalOcean vs CLI)
When using DigitalOcean managed Postgres, do not rely on CLI defaults. Set all --postgres-* flags explicitly:
- DO typical defaults:
- Port:
25060 - Database:
defaultdb - User:
doadmin
- Port:
- CLI defaults (if you omit flags):
- Port:
5432 - Database:
wlsly - User:
wlsly
- Port:
CLI Usage Notes
- Version check:
wlycli -vorwlycli --version(flag). There is noversionsubcommand. - Use
--base-dironly if you want a non-default location (default is~/.welley). The CLI will create the directory if it has permission. - Non-interactive SSH runs should include
--emailand any required flags to avoid prompts. - If the user provides a CLI binary on the server (e.g.
~/wcli), use it directly. Do not relocate it unless asked. - Copy snippets verbatim from
references/digitalocean.mdfor all DO API calls. Do not rewrite, adapt, or improvise the Python parsing — even small changes (e.g. escaping, quoting) can break across shells. - SSH remote scripts: Always use
ssh host 'bash -s' << 'EOF'(quoted heredoc). Never usessh host "..."with double quotes — zsh expands$(...)and[...]locally before sending to the remote host.
Failure Recovery
If deployment create fails and you need to retry:
- Use
wlycli deployment delete --domain <domain> --yes-please-delete-all-the-datato clean up. - Delete will attempt container-based cleanup of root-owned files. If it still fails, remove the project directory under
--base-dirwith elevated permissions and re-run.
Preflight Port-80 Test (Optional)
If certbot fails, test port 80 reachability before re-running:
docker run --rm -d -p 80:80 --name test-http nginx:alpine
curl -s http://<domain>:80
docker rm -f test-http