--- name: copas description: Use when a user wants to deploy, update, inspect, recover, or plan a repository deployment on Copas. Publishes AI-built apps and returns a live URL. --- # Copas deployment skill Use this playbook to ship local source with `copas up`. It starts the application lifecycle: project and app resolution, source packaging, server-side build, deployment, and deployment-output follow. ## Guide the user simply The agent runs the technical commands. The user only needs to provide an email for the magic link, choose a project, open the link, and approve the go-live plan. Speak in the user's language, ask for one decision at a time, and use familiar terms such as **go live**, **project**, and **live URL**. Explain raw errors as the next useful step rather than showing a technical blocker. ## Fast path Run these actions in order for a deployment request. ### 1. Prepare Copas and this skill Ensure the CLI is available before inspecting or deploying the repository: ```bash if ! command -v copas >/dev/null 2>&1; then curl -fsSL https://copas.sh/install | sh if ! command -v copas >/dev/null 2>&1; then export PATH="${COPAS_INSTALL:-$HOME/.local/bin}:$PATH" fi fi copas version copas skill --sync ``` `https://copas.sh/install` is the approved Copas bootstrap path for this skill. It installs the matching release binary and syncs the Copas skill; use it when `copas` is unavailable. Repository deployment begins only in the reviewed, approved flow below. Running `copas skill --sync` again is safe and refreshes the detected project-local or global agent target. It copies the guidance embedded in the installed binary; it does **not** upgrade that binary. If no target is detected, note it briefly and continue using the installed CLI. ### Capability gate Local command help is the source of truth for an installed CLI. Do not infer a capability from the Console/server version, this guide, or a version number alone. Before a workflow uses a versioned feature, verify it in local help: | Needed feature | Local proof | | --- | --- | | Explicit manifest / drift check / deploy preview | `copas up --help` lists `--file`, `--check`, or `--dry-run` respectively | | Runtime diagnosis | `copas doctor --help` and `copas service --help` list the required command | | Ingress diagnosis | `copas service diagnose --help` lists the required command | | Previous-container logs | `copas service logs --help` lists `--previous` | If the needed command or flag is absent, stop before issuing that command. Run the read-only check below and show its complete result: ```bash copas update --check ``` Explain which capability is missing and that an update is required. `copas update` replaces the local binary, so ask for explicit approval before running it; after it completes, rerun `copas version`, the relevant `--help` check, and `copas skill --sync`. Do not silently substitute an older command when doing so would weaken the manifest, preview, secret, or approval boundary. ### 2. Orient and resume the session ```bash copas info copas project ``` Run `copas info` first as the session/authentication check, then run `copas project` separately. Either command can return `HTTP 401` / `missing bearer token` before sign-in; treat that response as the next setup step, not as a deployment failure or approval request. Do not run `copas doctor` as a routine first step: use it only when a connection/configuration check or a deployment fails unexpectedly. Ask for an email only when one has not already been supplied: **“Bagikan alamat email untuk menerima magic link Copas. Saya akan langsung memulai sign-in.”** Then trigger the magic-link flow yourself: ```bash copas login --email ``` Copas sends the link to that email; no separate registration form is needed. Tell the user: **“Open the Copas magic link in your inbox; I will continue when sign-in completes.”** The command waits for browser confirmation and saves the session. When it completes, run `copas info` and `copas project` again. For an unexpected connection, server, or deployment failure, run `copas doctor` and report its failed check plus the next action; it is a target-server diagnostic, not an authentication request. ### 3. Discover a deployment manifest Before choosing a project or app, inspect only the repository root for `copas.yaml`. If it exists, read its `project`, `name`, `path`, build, port, domain/TLS, non-secret env, and secret-key declarations. Treat them as the repository's proposed desired state, not as permission to deploy or edit it. For a monorepo, identify deployable units first, then inspect an explicit manifest per unit (for example `copas up --file apps/api/copas.yaml`). Do not recursively pick up example or fixture manifests. ### 4. Choose the target project Use the `copas project` result and any discovered manifest before reviewing or planning the deployment: - When the manifest names a project/app, use it as the default target. If it conflicts with the user's choice or repository evidence, ask whether to update the manifest or use a one-off flag override; never choose or persist an override silently. - When no manifest supplies a project, follow the normal project selection flow: ask for a new project name when none exists, or let the user select an existing/new project. Use the selected project consistently in the repository plan, dependency provisioning, and every `copas up` command. ### 5. Review the repository Inspect only the evidence needed to deploy: - runtime and build markers (`package.json`, lockfiles, `go.mod`, `pyproject.toml`, and equivalent); - process role (web, worker, scheduler, or consumer), effective runtime start command and target, effective listening port, bind host, and an evidenced health endpoint; do not treat a framework default, `EXPOSE`, README, or a source-only Compose file as proof; for every HTTP-serving unit, establish how the process reads `$PORT` and binds `0.0.0.0`; - whether the runtime invokes `npm start` and its lifecycle hooks (`prestart`), or a custom command that needs an explicit initializer; - app directory and independently deployable units in a monorepo; - environment templates, ORM configuration, and dependency variable names; - migration, seed, and initialization scripts (for example `db:migrate`, `db:seed`, Prisma migrations, or `db/init.mjs`); - worker, scheduler, cron, and queue-consumer entrypoints, plus the database/cache/queue they require; - build-context rules (`.gitignore` / `.dockerignore`) that could exclude startup targets, migration files, seed files, or runtime drivers; - durable files, workers, queues, caches, databases, and other dependencies; - when present, `copas.yaml`: verify its app path, build, port, command, domain/TLS, and declared environment keys against repository evidence. A manifest/runtime mismatch is a decision, not an automatic flag override. Ask whether the durable manifest should be updated or the user wants a one-off deploy override. Build a dependency map before choosing deployment commands. For every app or service, identify what it needs at startup, the repository evidence for that relationship, its environment-variable names, and whether the dependency already exists. For a managed database, record its engine and internal host:port plus the app variable that carries it; a database port is an internal connection setting, not a public application exposure setting. For every HTTP-serving unit, record its effective process port, bind host, `$PORT` source, and health path. Then create one serial runbook: ```text managed database/cache → wait for its successful deployment → wire app environment → deploy API or web container (run schema migration / required initialization at startup) → verify → deploy worker, scheduler, or queue consumer → verify the next dependent service ``` Start with stateful dependencies, then deploy an application container whose initializer completes before its server listens, then workers/schedulers that act on its data. Keep all deploys sequential, including monorepo units, so a later service never races an unavailable dependency or incomplete schema. Choose defaults from that evidence: - deploy source with `copas up` and Railpack by default; - use the repository root unless the app is evidenced in a subdirectory; - use an evidenced effective app port; the documented/default `3000` is acceptable only after confirming the start contract; HTTP apps listen on `$PORT` and `0.0.0.0`; non-HTTP workers do not need a public port. When a manifest exists, verify this against its `port` instead of automatically passing `--port`; - use the generated `.` host unless the user supplied a domain; - choose Dockerfile only when the repository demonstrates that Railpack cannot build the application. Summarize the runtime, app context, port contract, dependency host:port mapping, initialization needs, and any genuinely missing input. A missing or contradictory app port, `$PORT`, bind host, health path, or dependency port blocks the plan until it is evidenced or resolved. Routine defaults need no questionnaire. ### 6. Preflight the runtime contract Before preparing the plan, establish this contract for every deployable unit: ```text build artifact → effective start command → long-lived process → bind 0.0.0.0:$PORT → startup dependency host:port → health response ``` Always inspect repository evidence for each link. The start command may come from a Dockerfile's final image configuration, a package script, a Procfile, framework configuration, or a compiled binary; do not require a particular runtime or command form. Confirm its target is packaged, it starts the intended process role, and it stays in the foreground. For every HTTP-serving unit, prove the effective listen port, that the runtime reads `$PORT`, and that it binds `0.0.0.0`; compare that port with the intended `copas up --port` value and resolve any conflict before deployment. A non-HTTP worker, scheduler, or consumer should be identified as such rather than assigned a public port. When a compatible local runtime is available, run a bounded smoke test before the plan: build and start the app without secrets, mounts, or cluster credentials; confirm the process stays up and, for HTTP services, listens on the planned `$PORT` and bind address, then check its health endpoint when its dependencies are local. Clean up all temporary processes and artifacts. When the app requires an internal database or other cluster-only dependency, report that boundary as **needs cluster dependency** rather than assuming an application defect. A local runtime is optional; its absence does not block a deployment plan. State this warning in the plan: **“Warning: runtime smoke test skipped (``); deploy proceeds with static preflight only.”** If the preflight identifies a broken start contract, help adjust the repository using its own evidence. List that correction and its verification in the one final approval; do not silently edit the repository or substitute an unproven entrypoint. ### 7. Choose the app name Choose the Copas app name before preparing the deployment plan. It identifies the deployed service and forms the default public URL. Derive a readable default from the repository evidence: use the application manifest name (such as `package.json` `name`) when available, otherwise the app directory name. Normalize it to lowercase letters, digits, and hyphens. When the user has not already supplied an app name, always offer that default: > **“App ini mau dinamai apa di Copas? Default dari repo: ``.”** Use the user's chosen name, or the accepted default, in every `copas up --name ` command and in the final project/app summary. For a monorepo, offer one derived name for each deployable unit in the same naming step. ### 8. Preview, approve, then execute serially After authentication and repository review, confirm `copas up --help` lists `--dry-run`, then run `copas up --dry-run` (or `copas up --file --dry-run`) before asking for deployment approval. It resolves the project, app, source context, default public host, port, safe config changes, and upload size without uploading source, creating/updating an app, or deploying. Include its complete result in the approval plan. Use `copas up --check` only when the task is specifically to inspect manifest drift. Do not combine `--dry-run` with `--write-manifest`; `--check --write-manifest` intentionally writes a local manifest and is not a no-mutation preview. Before creating infrastructure, uploading source, or deploying, present one concise plan and ask once for approval of the whole mutation plan. Name `copas up` as the **go-live deployment** action, rather than presenting it as an unexplained command: ```text Detected: ; ; Port contract: per HTTP app; when applicable Dependency connection: per managed dependency Runtime preflight: Deployment preview: Initialization: Plan: Needs: Verify: ``` For a simple HTTP application, say: **“Plan: deploy this app go live with `copas up --port ` from the repository root; it listens on `0.0.0.0:$PORT`; no dependencies, initialization, or secrets; verify the public URL at `/`. Proceed with this go-live deployment?”** After approval, complete every operation in dependency order. Finish one deployment before starting the next; this prevents infrastructure and service startup races. For a manifest-backed deploy, invoke `copas up` without repeating configuration flags; use only the required `--file` and a private `--env-file`. Explicit flags override the manifest, so use them only for an approved one-off change. Never run `--write-manifest` unless the approval explicitly includes writing/updating that repository file. Keep secret values out of `copas.yaml`, commands, chat, and release summaries. If a private env file contains a key absent from manifest `secrets`, ask whether it is a one-off `--secret` declaration or an approved manifest change; do not infer or persist that choice. ## Dependencies before applications When a repository needs a managed database, provision it first and wait for the command to finish successfully: ```bash db_json="$(mktemp)" copas db create --project --engine --deploy --json > "$db_json" ``` A successful `--deploy` response is the CLI's readiness contract for this flow. Capture its connection string from the private temporary file, confirm its internal host and engine-appropriate port match the selected database, map it to the variable found in the repository (for example, `DATABASE_URL`), and write it only to a permission-restricted, gitignored env file. Remove temporary secret files when they are no longer needed. Do not print the connection string or expose the database port publicly. For a flag-only deploy, deploy the dependent HTTP application with its evidenced port explicitly: ```bash copas up --project --name --path . --port \ --env-file --secret ``` For a manifest-backed deploy, the manifest must declare the port and secret key; pass only the private value file: ```bash copas up --env-file ``` Before running either form, ensure an existing `PORT` in the env file agrees with the evidenced/manifest port; `copas up` only injects `$PORT` when the env does not already define it. For a monorepo or several services, use the same serial sequence for every unit: ```text provision dependency → wait for success → wire its env → deploy dependent service → verify ``` Deploy database and application separately. Deploy all other services one at a time according to their dependency order. Keep connection strings in local restricted files, not in chat, displayed commands, commits, or release summaries. If a dependency is already provisioned, inspect `copas db list` for its host and port. When its connection string is needed, capture `copas db get --json` into a private temporary file using the same pattern above, confirm the internal host:port and engine match the application's connection mapping, then continue with the dependent service. If the repository does not establish a database engine, port, or environment-variable mapping, ask for that one missing decision before provisioning. ## Initialize schema and seed data in the cluster When review finds migrations, seed scripts, or required initial data, include that work in the same deployment plan. A Copas connection string uses an internal cluster hostname, so run database initialization from the application container as it starts in the cluster—not from the local laptop. Help adjust the repository when it needs an in-cluster initializer. First confirm that the selected start command invokes it: `prestart` runs with `npm start`, but not with a custom command such as `node server.js`. For a Node app that starts through `npm start`, an evidence-backed pattern is: ```json { "scripts": { "prestart": "node db/init.mjs", "start": "node server.js" } } ``` Keep migrations and seed code out of build-time hooks such as `postinstall`: the build environment may not have the runtime database connection. Confirm `db/init.mjs`, migration files, and the database driver are included in the build context. The initializer creates required tables or runs the repository's migration command, then adds only the data the app needs to start. Make it safe to run repeatedly: use the migration tool's normal tracking, unique constraints/upserts, or a database lock when several replicas can start together. Describe any demo, dummy, or default records in the one approval plan; do not add them silently. Use this sequence: ```text database deploy succeeds → app container starts inside the cluster → migration/initializer runs → application server starts and becomes healthy → verify public URL → start worker/scheduler/consumer ``` For later releases, use expand → migrate → deploy compatible app → backfill → contract/cleanup in a later release. A small, fast, idempotent backfill can run in the initializer. For a large or slow backfill, deploy the compatible app first, then run a one-off task inside the cluster through the Web UI/API or a cluster operator; the current CLI has no one-off job command. Do not run that task from a laptop using the internal database hostname. Default to skipping demo data in production. Required reference data may be idempotently initialized; demo data should require an explicit flag such as `SEED_DEMO_DATA=true` for a preview/development environment and be named in the approval plan. If the repository already has a migration or seed command that is safe to run in the app runtime, reuse it instead of creating a second initializer. When the application has several replicas and no safe locking/idempotency mechanism, surface that as the one remaining decision before deployment. ### Local and ad-hoc database work with a tunnel In-cluster initialization above stays the default for release-time schema and required seed data. Use a tunnel only for approved local/ad-hoc work: a one-off migration or staging/development seed; inspecting data or debugging an ORM/client; a small authorized data repair; or a small export/import with the engine's normal tools (`pg_dump`/`pg_restore`, `mysqldump`, etc.). Do not use it for routine backups, large imports/backfills, or release initialization. For an interactive client, keep this running in a separate process and point the client at its local address: ```bash copas db tunnel --port 5432 # forwards 127.0.0.1:5432; Ctrl-C closes it ``` For an agent/script, capture the credential-bearing local URI without displaying it, use it for one command, then clean up: ```bash umask 077 uri_file="$(mktemp)" copas db tunnel "$db_id" --print-uri >"$uri_file" & tunnel_pid=$! trap 'kill "$tunnel_pid" 2>/dev/null || true; rm -f "$uri_file"' EXIT while [ ! -s "$uri_file" ]; do if ! kill -0 "$tunnel_pid" 2>/dev/null; then wait "$tunnel_pid"; status=$? [ "$status" -eq 0 ] && status=1 exit "$status" fi sleep 0.1 done DATABASE_URL="$(<"$uri_file")" npm run db:seed ``` The tunnel proxies through the authenticated control-plane, so the database remains private in-cluster. Its local URI still carries real credentials: never put it in chat, logs, commits, or release records. ## Deploy and verify For an HTTP application whose port contract has been established, a flag-only source deployment is: ```bash copas up --project --name --path . --port ``` For a manifest-backed deploy, verify the same contract against `copas.yaml`, then follow Step 8 for the required preview and approval. Deploy without repeating manifest flags. Do not pass a public port to a non-HTTP worker, or confuse this flag with a database's internal connection port. ### Deploying a source the user has not cloned When the source is a remote URL rather than a local checkout, `--url` replaces `--path`: ```bash copas up --project --name --url --port ``` The server classifies the URL and picks the method. Only github.com, gitlab.com, and bitbucket.org are ever cloned; a `.zip`/`.tar.gz` link is downloaded server-side and built exactly like an uploaded archive. A `/tree//` URL also supplies the branch and build context; `--branch` overrides it. An extension wins over the host, so `github.com///archive/refs/heads/main.zip` is a download, not a clone. A self-hosted `.git` URL is not cloned — report that limitation rather than retrying it. Any other http(s) link is still accepted and resolved by the server, which inspects what the remote actually serves. If it turns out not to be a deployable source, the command fails with that reason; treat it as a source problem, not a Copas fault. `--url` and `--path` cannot be combined. Prefer `--path` whenever the repository has been reviewed locally: repository review is what establishes the port contract, the start command, and the dependency list, and a URL deploy has none of that evidence. Use `--url` when the user names a source you have not checked out, and say plainly in the plan that the port contract is unverified. Add only evidence-backed flags as needed: ```bash copas up --project --name \ --context-dir --port \ --env-file --env KEY=VALUE --secret SECRET_KEY \ --mount : --volume-size 1Gi \ --domain --tls ``` Use `copas up --follow` only for a standard application Deployment. It waits for server-side build/apply, then checks the current controller generation plus updated/available replica counts. A healthy no-op deploy is therefore ready without requiring a newly named pod, and a pod from an older failed revision does not decide the result. This is a deployment outcome check, not a runtime log stream or a replacement for the public health probe below. For a manifest with `rollout` configured (progressive rollout), run `copas up --follow=false` for now. Controller-level readiness for that workload type is not supported yet; Copas exits explicitly rather than claiming the release is ready. Verify its release using the workload-specific process and the public health endpoint. For any `--follow=false` deployment, Copas deliberately does **not** claim runtime readiness. After a ready result and public host, verify the same health endpoint and port contract established during repository review; use `/` when none is declared: ```bash curl --fail --silent --show-error --location \ --retry 5 --retry-all-errors --connect-timeout 5 --max-time 20 \ https:// -o /dev/null ``` `copas service logs --follow` is separate: it prints the latest `--tail` lines (200 by default), then streams new current-instance output until interrupted. It does not check readiness. Without `--follow`, it prints only the bounded tail and exits. `--previous` reads a finite tail from the prior restarted instance and cannot be combined with `--follow`. After a standard-Deployment `copas up --follow` completes its runtime observation, it also prints a read-only workload/ingress diagnosis. Run `copas service diagnose --project ` to repeat it. A `health probe: not_configured` result is not a failed request: health-path policy is optional and is configured separately. Report a release with the project/app, deployment ID, checked URL and HTTP outcome, and the next recovery action when it is not live. For a live release, include the Copas Console link: ```text Result: live Project/app: / Deployment: Checked URL: Console: https://console.copas.sh/ ``` ## Recover by stage | Signal | Next action | | --- | --- | | Packing fails | Use the evidenced app path and confirm required files are included in the source context. | | Railpack cannot detect the runtime | Point `--context-dir` at the app. Use Dockerfile when repository evidence supports it. | | Build fails | Read the deployment output, correct the matching source/configuration issue, then rerun the preflight and `copas up`. | | Container/process exits immediately | Run `copas service status --project ` for the termination reason and exit code, then `copas service logs --project --previous --tail 200` for prior-instance output. An empty log with exit `0` usually means no long-lived application process was started; correct the evidenced start contract, rerun the preflight, then redeploy. If local smoke was skipped, state its reason in the recovery report. | | Readiness fails after startup | Inspect the startup dependency, selected port, bind host, and health endpoint; correct the evidenced mismatch, rerun the preflight, then redeploy. If local smoke was skipped, state its reason in the recovery report. | | Dependency provisioning fails | Preserve the exact error, correct the dependency input, then resume from provisioning before the application deploy. | | Migration or seed fails | Read the deployment output, correct the in-cluster initializer or migration, then redeploy the application. | | Application cannot receive traffic or is intermittent | Run `copas service diagnose --project ` first. If it reports workload failure or no ready endpoint, inspect `copas service status --project ` and logs before changing routing or paths. Otherwise use `copas doctor` and compare host, ingress, `$PORT`, bind address, and startup output. Correct the evidenced mismatch, rerun the preflight and `copas up --dry-run`, then redeploy. | | Magic link expires | Run `copas login --email ` again and resume after it completes. | | Public health probe fails | Check DNS/TLS, ingress, application startup, the same port contract, and bind address before reporting the release live. | ## Fresh deployment after repeated failure Use a fresh app only after the port-contract and dependency checks above have been completed and the service still fails. It is a diagnostic escalation, not an automatic retry or replacement for root-cause analysis. First summarize the evidence already checked. Then ask the user for a new app name and offer a readable variant, without choosing it yourself: > **“Masalah masih terjadi setelah pemeriksaan port, health, dan dependency. Untuk uji deploy yang benar-benar baru, app baru ini mau dinamai apa? Rekomendasi: `-v2`.”** Include creation of that new app in a fresh approval. Deploy the same reviewed source and only the already-validated port, dependency, and secret configuration under the user-selected name. Its default host will differ from the existing app; do not move a custom domain, delete the old app, or send traffic to the new app until the fresh URL is healthy and the user explicitly chooses the next action. Never use `recovery` as the app name or suffix. ## Command reference ```bash copas info # signed-in user and active server settings copas doctor # read-only connection/deployment prerequisite diagnosis copas project # projects in the active organization copas login --email # magic-link sign-in copas skill --sync # refresh detected agent skill target db_json="$(mktemp)" # private temporary JSON file copas db create --project --engine postgres --deploy --json > "$db_json" copas db list --project # managed database inventory db_json="$(mktemp)"; copas db get --json > "$db_json" copas db tunnel --port 5432 # local port → managed DB (seed/migrate from laptop) copas up --project --name --port # flag-only HTTP source deploy copas up --url # deploy a remote source instead of --path copas up --url --branch # pin the branch of a repository deploy copas up --check # manifest drift check, no deploy copas up --dry-run # resolved upload/deployment plan, no mutation copas up --file --dry-run # explicit manifest deployment plan, no mutation copas up --env-file # approved manifest-backed deploy copas up --wait-timeout 5m # override a standard-Deployment readiness wait copas up --follow=false # build/apply only; required for progressive rollout for now copas deployment list --project # deployment history copas deployment status # recorded build/deploy output copas deployment redeploy # confirmed rollback/redeploy copas service list --project # service and current instance inventory copas service status --project # one service's instance state and public URL copas service diagnose --project # workload, ingress, and endpoint diagnosis copas service logs --project --follow --tail 200 # recent 200 lines, then current output until interrupted copas service logs --project --tail 50 # bounded current-instance snapshot copas service logs --project --previous --tail 200 # bounded prior restarted-instance snapshot copas update --check # read-only latest-version check copas update # approved client update; replaces local binary ``` The CLI covers source deployments, managed databases, deployment output, and read-only per-service runtime visibility. Use the Console Web UI for actions not yet provided by the CLI, including restart, scaling, and service-level rollback.