README.md

# CoolifyEx

<p align="center">
  <img src="assets/coolify_ex.svg" alt="CoolifyEx logo" width="200" />
</p>

<p align="center">
  <a href="https://github.com/nshkrdotcom/coolify_ex"><img src="https://img.shields.io/badge/release-0.1.0-0f766e.svg" alt="Release 0.1.0" /></a>
  <a href="https://hexdocs.pm/coolify_ex/"><img src="https://img.shields.io/badge/docs-hexdocs-2563eb.svg" alt="HexDocs" /></a>
  <a href="https://github.com/nshkrdotcom/coolify_ex"><img src="https://img.shields.io/badge/license-MIT-111827.svg" alt="MIT License" /></a>
  <a href="https://github.com/nshkrdotcom/coolify_ex"><img src="https://img.shields.io/badge/github-nshkrdotcom%2Fcoolify__ex-24292f?style=flat&logo=github" alt="GitHub" /></a>
</p>

`CoolifyEx` is an Elixir library and set of Mix tasks for triggering an existing Coolify application, waiting for the deployment to reach a terminal state, and then running smoke checks against the live app. It does not create applications in Coolify, replace your Dockerfile or build strategy, or act as a CI/CD system; the main use case is an operator-driven deploy from a trusted workstation or remote server that already has Git, Mix, and the right credentials.

## How It Fits in Your Stack

Your Git repository stays the source of truth. A local manifest tells `CoolifyEx` which Coolify application UUID to deploy, which branch must be current, which smoke checks to run, and which values to resolve from the environment. `CoolifyEx` can then push Git, call the Coolify API, poll deployment status, and finally verify the live URL that Coolify is serving.

```text
Git repo on trusted host
  |
  | load manifest + resolve {:env, "NAME"} tuples
  v
CoolifyEx (Mix task or library call)
  |
  | optional git push remote branch
  | start deployment via Coolify API
  v
Coolify deployment
  |
  | poll deployment UUID until success or failure
  v
Running application
  |
  | GET/HEAD smoke checks
  v
Verification result
```

This flow keeps deployment intent in your repository while leaving the actual build and runtime environment under Coolify's control.

## Prerequisites

- A running Coolify instance that you can reach from the deployment machine.
- An application that already exists in Coolify; `CoolifyEx` does not create it.
- API access enabled in Coolify.
- A Coolify API token with permission to start deployments.
- The application UUID for each Coolify app you want to trigger.
- Elixir `~> 1.18`, Mix, Git, and `curl` on the machine that will run the Mix tasks.
- Network access from that machine to the Coolify panel, the Git remote, and each public URL you plan to smoke-check.

## Installation

Add `coolify_ex` to your dependencies:

```elixir
def deps do
  [
    {:coolify_ex, "~> 0.1.0", runtime: false}
  ]
end
```

This adds `CoolifyEx` as an operator tool instead of a runtime application process, which matches how the library is used by Mix tasks and deploy scripts.

`CoolifyEx` targets Elixir `~> 1.18`; its runtime dependencies are `Req` and `Jason`, and its dev/test dependencies are `credo`, `dialyxir`, `ex_doc`, and `bypass`.

## Quick Start

1. Enable API access in the Coolify UI.

```bash
# Coolify UI
# Settings -> Configuration -> Advanced
# Enable API access, then save the change.
```

This turns on the API endpoints that `CoolifyEx` calls during deploy and status checks.

2. Create a token with deployment access.

```bash
# Coolify UI
# Keys & Tokens -> API Tokens
# Create a token that can start deployments, then copy it somewhere safe.
```

This gives the deployment machine a bearer token for the Coolify API.

3. Copy the application UUID from Coolify.

```bash
# Coolify UI
# Open the application you want to deploy.
# Copy the application UUID that identifies this app in the API.
```

You need one UUID per manifest project entry.

4. Add the dependency and fetch it locally.

```elixir
def deps do
  [
    {:coolify_ex, "~> 0.1.0", runtime: false}
  ]
end
```

This makes the Mix tasks and library code available in your project.

```bash
mix deps.get
```

This installs `CoolifyEx` and its dependencies into the current project.

5. Run the bootstrap script from the repository root.

```bash
./scripts/setup_remote.sh
```

This checks for `git`, `curl`, and `mix`, copies `coolify.example.exs` to `.coolify_ex.exs` if that file does not exist yet, runs `mix deps.get`, and then runs `mix coolify.setup --config .coolify_ex.exs`.

6. Export the environment variables that the manifest will resolve.

```bash
export COOLIFY_BASE_URL="https://coolify.example.com" # replace this
export COOLIFY_TOKEN="coolify-api-token" # replace this
export COOLIFY_WEB_APP_UUID="00000000-0000-0000-0000-000000000000" # replace this
```

These values stay on the trusted machine and are read at manifest load time through `{:env, "NAME"}` tuples.

7. Copy the shipped example manifest if you did not use the bootstrap script.

```bash
cp coolify.example.exs .coolify_ex.exs
```

This creates the default manifest file name that `CoolifyEx` will discover first.

8. Edit the manifest with the real UUIDs, branch, and smoke-check URLs.

```bash
${EDITOR:-vi} .coolify_ex.exs
```

This is where you bind your local repository to one or more Coolify applications.

9. Trigger the first deployment.

```bash
mix coolify.deploy
```

On success, the task prints `Deployment finished: DEPLOYMENT_UUID` and then `Verification passed: PASSED/TOTAL checks` unless you used `--skip-verify`.

## Example Manifest

```elixir
%{
  # Present in the shipped example; the loader currently ignores this key.
  version: 1,
  # Coolify panel URL, resolved from the local shell environment.
  base_url: {:env, "COOLIFY_BASE_URL"},
  # Coolify API token, also resolved from the local shell environment.
  token: {:env, "COOLIFY_TOKEN"},
  # Project selected when you omit --project.
  default_project: :web,
  projects: %{
    web: %{
      # The Coolify application UUID for this project entry.
      app_uuid: {:env, "COOLIFY_WEB_APP_UUID"},
      # Branch that must be checked out locally before deploy unless you use --no-push.
      git_branch: "main",
      # Git remote used for the optional push step.
      git_remote: "origin",
      # Use "." for a top-level app or a relative child path for a monorepo app.
      project_path: ".",
      # Public URL used to expand smoke-check paths such as "/healthz".
      public_base_url: "https://example.com", # replace this
      smoke_checks: [
        # GET https://example.com/ and expect HTTP 200.
        %{name: "Landing page", url: "/", expected_status: 200},
        # GET https://example.com/healthz, expect HTTP 200, and require "ok" in the body.
        %{name: "Health", url: "/healthz", expected_status: 200, expected_body_contains: "ok"}
      ]
    }
  }
}
```

This is the shipped `coolify.example.exs` with inline comments describing how each field affects loading, deployment, and verification.

## Mix Tasks At A Glance

| Task | What it does | Example |
| --- | --- | --- |
| `mix coolify.setup` | Prints a local or remote-server checklist, checks for `git`, `curl`, and `mix`, and tries to load the manifest. | `mix coolify.setup --config .coolify_ex.exs` |
| `mix coolify.deploy` | Optionally pushes Git, starts a Coolify deployment, waits for completion, and optionally verifies smoke checks. | `mix coolify.deploy --project web --force` |
| `mix coolify.status` | Fetches one deployment by UUID and prints its status plus the Coolify logs URL when available. | `mix coolify.status DEPLOYMENT_UUID` |
| `mix coolify.logs` | Fetches one deployment by UUID and prints normalized log lines. | `mix coolify.logs DEPLOYMENT_UUID --tail 50` |
| `mix coolify.verify` | Runs the manifest's smoke checks without starting a new deployment. | `mix coolify.verify --project web` |

## Key Behaviors

- Relative smoke-check URLs are expanded only when the URL starts with `/` and `public_base_url` is a string. Otherwise the URL is kept exactly as written.
- `mix coolify.deploy --no-push` skips the Git push step but still loads the manifest, starts the deployment, waits for Coolify, and verifies unless you also pass `--skip-verify`.
- `project_path` must point to an existing directory when the manifest loads, but Git pushes always happen from `repo_root`, which is the directory that contains the manifest.
- If the Coolify deployment succeeds and a smoke check fails afterward, `mix coolify.deploy` raises `Verification failed with N failing checks`; it does not roll back or mark the deployment itself as failed in Coolify.
- Manifest loading is eager. If any `{:env, "NAME"}` tuple resolves to `nil` for a required field, the whole load fails before any task-specific work begins.

## Documentation

- [Getting Started](guides/getting-started.md) for the first end-to-end deploy from a trusted machine.
- [Manifest Format](guides/manifest.md) for file discovery, env tuples, path validation, and smoke-check rules.
- [Mix Tasks](guides/mix-tasks.md) for every CLI flag, success message, and failure mode.
- [Monorepos and Phoenix Apps](guides/monorepos.md) for one manifest that targets multiple deployable applications.
- [Remote Server Setup](guides/remote-server.md) for keeping credentials off developer laptops and CI.

## License

`CoolifyEx` is released under the MIT License. See [LICENSE](LICENSE).