Migrating from Kamal to Coolify
Today, I migrated two of my Django (Pegasus) apps from Kamal to Coolify, and I wanted to tell you how it went. If you’re considering a similar move, read on for a breakdown of what to expect and let me know if you have any questions!
Setting up Coolify Link to heading
The setup process was pretty smooth. I got a fresh VPS from Hetzner and followed this excellent guide from CJ@Syntax to get everything up and running.
Deployment Link to heading
I opted for a Docker Compose-based deployment, which seems to work well for deploying a monolithic Django app with Celery (+ Celery Beat).
Steps to Deploy an App with PostgreSQL and Redis Link to heading
Here’s how I deployed my app with PostgreSQL and Redis, including some gotchas I spent hours figuring out:
- Create a New Project: Start by creating a new “Project” in the Coolify UI.
- Add Resources:
- Add a PostgreSQL resource. This can be done easily in the UI. Here’s the guide.
- Do the same for Redis.
- Add Your App Resource:
- Choose “Docker Compose” as the build option after linking your app repository.
- Here’s an example
docker-compose.yml
file I made (add it to your repository beforehand):
version: '3.8' services: my_app_web: build: context: . dockerfile: Dockerfile.web environment: PORT: ${PORT} DJANGO_SETTINGS_MODULE: my_app.settings_production POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} SECRET_KEY: ${SECRET_KEY} DATABASE_URL: ${DATABASE_URL} REDIS_URL: ${REDIS_URL} restart: unless-stopped ports: - "${PORT}:8000" celery: build: context: . dockerfile: Dockerfile.web command: celery -A my_app worker -l INFO --concurrency 4 environment: DJANGO_SETTINGS_MODULE: my_app.settings_production POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} SECRET_KEY: ${SECRET_KEY} DATABASE_URL: ${DATABASE_URL} REDIS_URL: ${REDIS_URL} celery_beat: build: context: . dockerfile: Dockerfile.web command: celery -A my_app beat -l INFO environment: DJANGO_SETTINGS_MODULE: my_app.settings_production POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} SECRET_KEY: ${SECRET_KEY} DATABASE_URL: ${DATABASE_URL} REDIS_URL: ${REDIS_URL} restart: unless-stopped
- Environment Variables: Add all necessary environment variables in the resource settings. You can copy the connection URLs for PostgreSQL and Redis from the UI after creating those resources.
- Connect to Predefined Network: Enable this option for your app resource to connect it to the same Docker network as your PostgreSQL and Redis resources.
- Domain Setup: Assign a domain for your Django app, including your
$PORT
. For example,https://app.coolstuff.com:8000
. N.B. Port 8000 is actually taken by Coolify itself. - Deploy: Hit deploy and check the logs to see if everything worked 🤞
Pros and Cons (So Far) Link to heading
Pros Link to heading
- Continuous Deployment: Push to deploy – I love it. Simply pushing your changes to GitHub to deploy is a much nicer experience than running commands and burning through your battery by building images with Kamal.
- Notifications: Easy to set up Telegram/email notifications for deployment events.
- Ease of Use: Deploying new apps is more straightforward compared to Kamal (IMO).
- Active Development: Coolify is moving quickly and adding new features all the time. Its community seems pretty cool, too.
- Cost-Effective: Running Coolify is much cheaper than using a commercial PaaS.
Cons Link to heading
- Beta Version: The current major version of Coolify is still in beta so it has some rough edges.
- Manual Setup: You can’t set up your entire stack with a single config file — you need to click around the UI quite a bit.
- UI Performance: The UI can be a bit slow, especially on smaller instances. Hopefully, this is just a “beta thing,” and the backend gets optimized soon.
- Port Conflicts: Port 8000 is reserved by Coolify itself. And since all the apps are running on the same host, different apps/resources can’t use the same port. This is only a minor annoyance, but still something to keep in mind.
That’s it for now! All in all, this was a fun experience, despite the fact that I hit a bug during installation and had to restart the process, and there were a few other hiccups* along the way.
*It would have probably been a smoother process had I been more patient and actually read the documentation but my nature is to jump in first and learn to swim later 😅