v0.3.0A project-local CLI for Coolify
Link once, then ship.
Link a repository to its Coolify application once, then deploy, tail logs, sync variables, and run locally from the terminal, without UUIDs.
curl -fsSL https://raw.githubusercontent.com/joaomnuno/coolship/main/scripts/install.sh | shThe whole loop, one binding
After link, no command needs a resource identifier. deploy submits one deployment and follows the UUID that submission returned, relaying the server's build log while it waits.
- Interrupting stops local waiting only; the deployment continues and its UUID is reported.
- Results go to stdout, progress and prompts to stderr, so piping stays useful.
- This replay is a real session against the example application, typed back at typing speed.
$ coolship link --project coolship-example --environment production --application coolship-example Linked project in /home/you/my-app/coolship.toml Application: coolship-example (mm4c0zpbrzx8z96t0qiw3tff) Environment: production Project: coolship-example Context: home $ coolship deploy → coolship-example → production ✓ Deployed 0:52 ✓ build 0:41 ✓ rolling update 0:08 ✓ container 0:06 ✓ cleanup 0:00 Deployment: 03dusayin5rleswixblvdqba Application: coolship-example (mm4c0zpbrzx8z96t0qiw3tff) Status: finished https://coolship.example.com $ coolship logs --lines 5 2026-09-10T14:15:01.017517155Z 127.0.0.1 - - [10/Sep/2026:14:15:01 +0000] "GET / HTTP/1.1" 200 182 "-" "Wget" "-" 2026-09-10T14:15:11.049948936Z 127.0.0.1 - - [10/Sep/2026:14:15:11 +0000] "GET / HTTP/1.1" 200 182 "-" "Wget" "-" 2026-09-10T14:15:21.089560996Z 127.0.0.1 - - [10/Sep/2026:14:15:21 +0000] "GET / HTTP/1.1" 200 182 "-" "Wget" "-" 2026-09-10T14:15:31.130467890Z 127.0.0.1 - - [10/Sep/2026:14:15:31 +0000] "GET / HTTP/1.1" 200 182 "-" "Wget" "-" 2026-09-10T14:15:41.185711659Z 127.0.0.1 - - [10/Sep/2026:14:15:41 +0000] "GET / HTTP/1.1" 200 182 "-" "Wget" "-"
Showing the final output; animation is off because your system prefers reduced motion.
Four verbs, one project
Everything reads the binding that link wrote. Every command takes --format json.
Link
Bind the repository to its Coolify application once: a small, credential-free coolship.toml you commit.
$ coolship link
coolship link docsDeploy
Submit one deployment, follow exactly that UUID, and stream the build log while it runs.
$ coolship deploy
coolship deploy docsEnv
Pull, diff, and push .env against one scope of the variables, never inventing withheld values.
$ coolship env pull
env pull, diff, and push docsDev
Run a local command with the application's runtime variables injected over your environment.
$ coolship dev -- npm run dev
coolship dev docs
A minute with Coolship
Five commands from a fresh terminal to a pull-request preview.
Step 1: Log in once
$ coolship loginCoolship verifies the URL and token against the server, then stores them in the same file coolify-cli uses, so a login in either tool is a login in both.
login referenceStep 2: Link the repository
$ coolship linkIt walks project, environment, and application, asking only when a choice is genuinely ambiguous, and writes coolship.toml. Every later command reads that binding.
link referenceStep 3: Deploy
$ coolship deployDeploy the source and branch already configured in Coolify, wait for exactly that deployment, and watch the build log stream while it runs.
deploy referenceStep 4: Pull the variables
$ coolship env pullWrite the application's variables into .env, keeping local-only keys and comments, and noting withheld values as comments rather than writing them empty.
env referenceStep 5: Preview a pull request
$ coolship preview --pr 42Deploy the preview Coolify already holds for the pull request and observe it like deploy; in a GitHub Actions pull_request job the number comes from GITHUB_REF.
preview reference
Complementary to coolify-cli, not a replacement
coolify-cli manages Coolify resources. Coolship manages the developer workflow around the project you are in. They share one login: both read the same credentials file.
| Task | with coolify-cli | with Coolship |
|---|---|---|
| Deploy |
|
Waits for that exact deployment and streams its build log. |
| Logs |
|
Polls snapshots and reports a gap instead of hiding it. |
| Variables |
|
One scope at a time; withheld values are never invented. |
| Status |
|
|
| Pull request preview |
|
|
| Servers, keys, teams |
| Out of scope on purpose. coolify-cli remains the right tool for administering the instance. |
The parts around deploy
Real samples from the reference. Each one is a command you can run today.
Several applications, one file
A repository with several applications uses named targets instead of [project]. Commands pick the target whose root contains the current directory, or take its name.
Monorepos guideversion = 1[apps.web]context = "home"project = "Personal"environment = "production"application = "frontend"root = "apps/web"[apps.api]context = "home"project = "Personal"environment = "production"application = "backend"root = "apps/api"cd apps/api && coolship deploy # the target whose root contains the directorycoolship deploy api # or name it from anywherecoolship logs web --followPull request previews, from the terminal or CI
Coolify must already know the pull request (enable Preview Deployments and let its GitHub webhook register it); preview deploys the preview it holds and observes it exactly like deploy.
Preview deploymentscoolship preview --pr 42coolship preview api --pr 42 # a named monorepo target, like deploycoolship preview # in a GitHub Actions pull_request job, reads GITHUB_REFon: pull_request:jobs: preview: runs-on: ubuntu-latest env: COOLSHIP_URL: ${{ secrets.COOLSHIP_URL }} COOLSHIP_TOKEN: ${{ secrets.COOLSHIP_TOKEN }} steps: - uses: actions/checkout@v4 - name: Install Coolship run: | curl -fsSL https://raw.githubusercontent.com/joaomnuno/coolship/main/scripts/install.sh | sh echo "$HOME/.local/bin" >> "$GITHUB_PATH" # The pull request number is read from GITHUB_REF; --pr overrides it. - name: Deploy the preview run: coolship previewEvery step a command performs, reported
Configuration, Git boundary, binding, credentials, context, server reachability and version, and whether the binding resolves to a running application. Exit status 1 when any check fails.
doctor reference$ coolship doctor[ok] Project configuration: /home/you/my-app/coolship.toml[ok] Git repository: /home/you/my-app[ok] Binding: Personal / production / fenix-bot in /home/you/my-app[ok] Credentials: /home/you/.config/coolify/config.json (1 instance, default home)[ok] Context: home at https://coolify.example.com[ok] Server: Coolify 4.3.18[ok] Application: fenix-bot (9f8e7d6c) is running:healthyRun locally with the application's variables
The runtime variables are injected over your environment, shared references resolved, so a process sees what it would see on Coolify without pulling a .env first. The child's exit status becomes Coolship's.
dev referencecoolship dev -- npm run devcoolship dev api -- go run .coolship dev # runs the binding's dev setting[project]dev = "npm run dev"Show and replace the application's domains
Coolify generates a domain from the application UUID until you set your own. domain set shows the change, asks first, refuses a domain in use elsewhere unless forced, and reads the application back to confirm what the server kept.
domain referencecoolship domaincoolship domain set app.example.com # bare host means https://coolship domain set https://app.example.com https://www.example.com --redirect non-www{ "plan": { "target": { "…": "the resolved target" }, "current": ["https://9f8e7d6c.coolify.example.com"], "domains": ["https://app.example.com", "https://www.example.com"], "redirect": "non-www" }, "warnings": ["The proxy learns the new domains on the next deployment."]}One result object per command
Results go to stdout and progress to stderr, so --format json composes with jq and scripts. Every result that names an application carries the same target object; logs prints newline-delimited events.
Output and exit codes{ "target": { "target": "default", "instance": "home", "instance_url": "https://coolify.example.com", "project": "coolship-example", "project_uuid": "rxv3lqhdvuprnl433dczvo0s", "environment": "production", "environment_uuid": "5omkp5uuj0qpet6dy16r6bag", "application": "coolship-example", "application_uuid": "mm4c0zpbrzx8z96t0qiw3tff", "root": "/home/you/my-app" }, "status": "running:healthy", "url": "https://coolship.example.com"}{"type":"logs","logs":"2026-09-10T11:49:33.442427921Z 127.0.0.1 - - [10/Sep/2026:11:49:33 +0000] \"GET / HTTP/1.1\" 200 182 \"-\" \"Wget\" \"-\"\n"}Verified against Coolify 4.3.18. Every command was run end to end against a live instance; 4.3.19 changes none of the endpoints Coolship uses.
What was verifiedTry it on the repository you have open right now
One line installs v0.3.0 into ~/.local/bin, checksum verified, never with sudo. Then coolship link.
curl -fsSL https://raw.githubusercontent.com/joaomnuno/coolship/main/scripts/install.sh | sh