ข้ามไปยังเนื้อหาหลัก

Migrate from Coolify

Coolify (and the bash-script setups it usually replaces) solve "get my code onto a server." Loha solves the same problem with a versioned pipeline, a build history, and an audit trail. This guide maps the concepts and walks you through the switch.

Unlike the GitLab CI and GitHub Actions guides, there's no config file to convert. Coolify's deploy is UI-configured, not YAML. So this guide is entirely about the conceptual mapping and a first-pipeline walkthrough, not a line-by-line port.

Why teams switch​

If you're running Coolify or a home-grown deploy script, the pain points that usually push teams to look elsewhere:

  • No real deploy history. A bash script that SSHes in and runs git pull && docker compose up -d doesn't leave a record beyond your shell's scrollback. Loha turns every deploy into a build with a stable id, live logs, and a permanent record of what ran and when.
  • Brittle, shared SSH keys. Script-based deploys typically share one SSH key across the team (or across every project) with no rotation story. Loha's secrets are encrypted at rest, injected only into the step that names them, and redacted from logs automatically.
  • No audit trail. "Who deployed this, and when?" is not a question a shell script can answer. Every Loha build records an actor (a person, or a service account for automated runs), and every secret access, registry change, and variable edit lands in the workspace audit log.
  • No easy rollback. Re-deploying a known-good version means finding the old script arguments or the right commit by hand. In Loha, deploying an older, known-good build is a matter of re-running with that build's image tag. See What's not yet 1:1 for the honest version of what "rollback" means today.

Before you start​

You will need:

  • The repository Coolify currently deploys, on a Git host Loha can connect to.
  • Whatever credentials your deploy currently uses (a registry token, an SSH key to your server, or a kubeconfig), so you can re-create them as Loha secrets.
  • Admin or Owner on the Loha workspace you're connecting into.
Your Git host is supported

GitLab, GitHub, and Gitea are all supported today via a personal access token: connect, build, and status reporting work the same way on all three. A one-click GitHub App install (no per-repo token) is still on the roadmap; until then, GitHub repos connect the same PAT-based way as GitLab and Gitea. See Repositories & Git hosts for current status.

Conceptual mapping​

CoolifyLoha
An Application (or Resource) tied to a repoA connected repository plus the jobs in its .loha.yml that build and deploy it
Git-push-to-deploy (Coolify's webhook watches your branch)Connect the repository, then a push trigger in .loha.yml. You add the generated webhook URL to your Git host once
Environment variables set per-application in the Coolify UIVariables and secrets: Plain or Secret, set at project, repository, or trigger scope
A Docker Compose file Coolify builds and runsA buildah-build step to build the image, pushed to a registry you add under Settings → Registries
Coolify's server-side deploy (it runs Compose on your VM)A deploy target on the project (VMs over SSH), or git-update-manifest if you deploy to Kubernetes through GitOps
One Coolify project per environment (staging server, prod server)One repository, with per-environment values set via trigger-scoped variables (see below)

The big structural difference: Coolify's "application" bundles the build and the deploy target into one UI object. Loha keeps them separate: a .loha.yml job builds an image once, and a deploy step (or several, one per environment) pushes it out. That separation is what lets the same build get promoted to a different environment instead of rebuilt for each one.

Step-by-step migration​

1. Connect your repository​

In the Loha dashboard: Workspace → Repositories → Connect repository. Pick your Git host, provide an access token, and select the repo. Then create a webhook trigger and add its generated URL + secret to your Git host. Loha adds that webhook for you when your access token allows it, and hands it to you to paste otherwise (see Repositories & Git hosts → Connecting a repository). See Repositories & Git hosts for supported providers and exact token scopes.

2. Write your .loha.yml​

Create .loha.yml at the repo root. A minimal build-and-push pipeline, structurally close to what Coolify's build step does for you today:

version: '1'
name: my-app

on:
push:
branches: [main]

jobs:
build:
resource_class: medium
steps:
- name: Checkout
uses: loha/git-clone@v1
- name: Build and push image
uses: loha/buildah-build@v1
with:
IMAGE: registry.example.com/acme/my-app:${COMMIT_SHA}

registry.example.com stands in for your registry. Loha builds your image and pushes it to a registry you bring, the same way Coolify pushes to whatever registry (or local Docker daemon) you configured it with. Add the credential under Settings → Registries before this job can push. See the full field list in the .loha.yml schema.

3. Define your environments​

Loha doesn't have a "environment" object the way Coolify does. You get there with triggers and trigger-scoped variables instead. The pattern from Variables and secrets, extended to three environments:

  1. Create three triggers on the repository: dev-deploy, uat-deploy, prod-deploy (one webhook trigger per branch, or three manual/workflow_dispatch triggers if you deploy on demand).
  2. On each trigger's detail page, set the values that differ per environment, e.g. a Plain variable DEPLOY_NAMESPACE (app-dev, app-uat, app-prod) and, if each environment has its own credentials, a same-named Secret (e.g. GITOPS_TOKEN) on each trigger. The most-specific-scope-wins rule means your .loha.yml reads the same variable name everywhere and gets the right value per trigger.
  3. Add a deploy job that reads those variables. See the next step.

4. Add a deploy step​

If Coolify is deploying Docker Compose to a VM today, create a deploy target for that VM (Project → Settings → Targets). The target keeps the VM's host and SSH key on the project, not in your pipeline. Deploy targets are behind a workspace feature flag: if you don't see the tab, ask your workspace owner.

If you're moving to Kubernetes, let your GitOps tool deploy, and have the pipeline update the manifest:

deploy:
needs: [build]
steps:
- name: Update manifest
uses: loha/git-update-manifest@v1
with:
GIT_REPOSITORY: https://git.example.com/acme/infra.git
IMAGE_NAME: registry.example.com/acme/api
IMAGE_TAG: ${COMMIT_SHA}
secrets:
- GITOPS_TOKEN

GITOPS_TOKEN comes from the trigger's Secret you set in step 3.

5. First build, first deploy​

Commit and push. The webhook fires, the dashboard shows a new build under Builds, and you can watch both jobs run with live logs. See Your first pipeline for the full walkthrough of the build view, and What just happened for what each step did under the hood.

What's not yet 1:1​

Be honest with yourself about a few gaps before you commit to a cutover date:

  • No one-click promote across environments. Coolify (and most PaaS tools) let you point-and-click "promote staging to production." Loha today gets you there with the trigger-and-variable pattern above: re-running the uat-deploy trigger's build against the same image tag that passed in dev is a manual step, not a button. Named environments with a built-in promotion/approval flow are a separate item on the roadmap. Check there for current status before assuming it exists.
  • No automatic rollback on a failed health check. Nothing today automatically reverts a deployment that fails its health check. A rollback means deploying an earlier image again, for example by re-running the build of the earlier commit. Build retry / re-run from failed re-runs a build, not a deploy rollback. Know the difference before you rely on either.
  • No approval gates between jobs. If your current process has a human click "approve" before a prod deploy runs, there's no equivalent job-level gate in .loha.yml yet.

None of this blocks the migration: it means "staging → prod" and "roll back a bad deploy" are processes you script with triggers and variables today, not a feature you click. If those gaps are launch blockers for you, the roadmap is the place to check target versions before committing to a date.

Where to go next​