--- name: deploy-phoenix description: Initialize and deploy a Phoenix app to a VPS. Use this skill when the user wants to deploy a Phoenix app, set up deployment infrastructure, add a new app to the server, or mentions deploying to staging/production. Also triggers on "deploy this app", "set up deployment", "add to VPS", "init phoenix deploy", or any request to get a Phoenix app running on the server. The user will typically be inside the Phoenix app's project directory when invoking this skill. allowed-tools: ["Bash", "Read", "Write", "Edit", "Glob", "Grep", "AskUserQuestion"] --- # Deploy Phoenix App Guide the user through deploying a Phoenix app to a VPS using the DevOps repo's deployment scripts. This skill is designed to be run from **within the Phoenix app's project directory**. ## Configuration - **DEVOPS_REPO**: Path to the DevOps repository. Detect by checking (in order): 1. `$DEVOPS_REPO` environment variable 2. `~/code/DevOps` (default convention) If neither exists, ask the user for the path. - Apps registry: `$DEVOPS_REPO/apps/apps.yaml` ## Step 1: Detect Project Info Gather what you can automatically before asking the user anything: 1. **DevOps repo**: Locate the DevOps repo using the detection order above. Read `apps/apps.yaml` to verify it exists. 2. **App name**: Read `mix.exs` in the current directory and extract the `:app` atom from `project/0`. Convert to snake_case string. 3. **GitHub repo**: Run `git remote get-url origin` and extract the `org/repo` portion. 4. **Project path**: Use `pwd` to get the current directory. This becomes the `path` field in apps.yaml if it differs from the default `~/Workspace/AppName` convention. 5. **Server details**: Read `defaults` from `apps/apps.yaml` to get server IP and SSH hostname. 6. **Next available ports**: Scan existing apps in `apps.yaml` for the highest used port. Present the detected info to the user for confirmation before proceeding. ## Step 2: Ask Questions Use `AskUserQuestion` to gather the remaining info. Ask these in a single, organized question — not one at a time: ### Required Info - **Production domain**: e.g., `myapp.com` or `myapp.example.com` - **Staging environment?**: Yes/No. Many apps only need production initially. - **Staging domain** (if yes): Default suggestion `{appname}-staging.example.com` - **Production branch**: Default `main` ### Auto-Assigned These are computed, not asked: - **Ports**: Scan `apps.yaml` for the highest used port and assign the next available. Production gets the next even number, staging gets production + 1. - **Database names**: `{appname}_prod` and `{appname}_staging` - **Staging branch**: Default `staging` (or `main` if no staging env) ### Example Question Format ``` I detected the following from your project: App name: my_app GitHub repo: myorg/MyApp Project path: /Users/me/projects/MyApp Next available ports: 4010/4011 Server: dev@ssh.example.com (123.45.67.89) I need a few more details: 1. Production domain? (e.g., myapp.com, myapp.example.com) 2. Do you need a staging environment? (yes/no) 3. If staging, staging domain? (default: myapp-staging.example.com) 4. Production branch? (default: main) ``` ## Step 3: Update apps.yaml Add the new app entry to `$DEVOPS_REPO/apps/apps.yaml`. Follow the existing format exactly. ### Production-only app: ```yaml appname: repo: org/RepoName path: /full/path/to/phoenix/app # only if non-standard location production: domain: app.example.com port: 4010 db: appname_prod branch: main ``` ### App with staging: ```yaml appname: repo: org/RepoName path: /full/path/to/phoenix/app # only if non-standard location production: domain: app.example.com port: 4010 db: appname_prod branch: main staging: domain: app-staging.example.com port: 4011 db: appname_staging branch: staging ``` ### Path field rules: - If the Phoenix app lives at `~/Workspace/AppName` (derived from the app name), **omit the path field entirely** - If the Phoenix app is in a subdirectory or non-standard location, use the **full absolute path** - Look at existing entries in apps.yaml for examples of both patterns After editing, show the user the new entry and confirm before proceeding. ## Step 4: Run init-phoenix-app.sh This script reads from apps.yaml and sets up the deployment infrastructure in the Phoenix app: ```bash $DEVOPS_REPO/scripts/init-phoenix-app.sh {appname} ``` **What it does:** - Copies deployment templates (mix tasks, nginx config, systemd service, build script) - Creates `config/deploy.exs` with environment-specific settings - Creates `.tool-versions` for asdf - Installs dependencies, creates dev database, runs tests - Initializes git repo and pushes to GitHub **Important**: This script is interactive — it runs `mix phx.new` which prompts for confirmation. If the app already exists, it skips creation and only updates templates. After running, present the output to the user and confirm success before continuing. ## Step 5: DNS Setup Reminder Before server setup, remind the user to create DNS records. Read the VPS IP from `apps.yaml` defaults: ``` Before server setup, create these DNS records: Production: Type: A Name: {production_domain} Content: {vps_ip} Staging (if applicable): Type: A Name: {staging_domain} Content: {vps_ip} ``` Ask the user to confirm when DNS is configured. The setup script also checks DNS, but it's better to have it ready. ## Step 6: Run setup-server-app.sh This script provisions the server and does the first deploy: ```bash $DEVOPS_REPO/scripts/setup-server-app.sh {appname} {environment} ``` Run for each environment the user configured. Start with staging if it exists, then production. **What it does:** 1. Verifies DNS resolution 2. Creates server directory structure at `/var/www/{domain}/` 3. Creates PostgreSQL database and user 4. Generates `.env` with secrets (SECRET_KEY_BASE, DATABASE_URL, etc.) 5. Runs first deploy (`mix deploy {env}`) 6. Installs systemd service 7. Installs nginx config 8. Runs certbot for SSL 9. Enables the service **Alternative**: If the user wants to do server setup incrementally, offer `--setup-only` which does steps 1-4 only. They can then deploy manually with `mix deploy {env}`. ## Step 7: Verification After setup completes, verify the deployment: ```bash # Check the app is responding curl -sI https://{domain} | head -5 # Check service status (read user/server from apps.yaml defaults) ssh {user}@{server} "systemctl status {appname}-{env}" ``` ## Summary Output After everything succeeds, present a summary: ``` Phoenix app deployed successfully! App: {appname} Production: https://{production_domain} Staging: https://{staging_domain} (if applicable) Subsequent deploys from this directory: mix deploy staging mix deploy production Useful commands: ssh {user}@{server} "journalctl -u {appname}-{env} -f" # logs ssh {user}@{server} "systemctl restart {appname}-{env}" # restart ``` ## Error Recovery Common issues and how to handle them: - **DNS not resolving**: The setup script will pause and wait. Tell the user to check their DNS provider. - **Port conflict**: If the auto-assigned port is in use, increment and try again. - **mix phx.new fails**: Usually means Elixir/Phoenix isn't installed. Check `mix --version` and `mix archive.install hex phx_new`. - **Certbot fails**: Usually DNS propagation. The script continues — user can run certbot manually later. - **Database exists**: The script is idempotent — it skips existing databases. ## Re-running Both scripts are idempotent. If something fails partway through, the user can safely re-run: - `init-phoenix-app.sh` will skip existing app creation and just update templates - `setup-server-app.sh` will skip existing directories/databases and re-deploy