DEV Community

Cover image for How to implement Coolify, the self-hosted alternative to Heroku
Megan Lee for LogRocket

Posted on • Originally published at blog.logrocket.com

How to implement Coolify, the self-hosted alternative to Heroku

Written by David Omotayo✏️

If you’ve been active on Twitter or Reddit lately, you’ve likely seen discussions surrounding Vercel’s pricing model, which has turned many success stories into nightmares.

For example, Cara, a creative platform, experienced a surge of 650,000 users overnight, resulting in a $95,000 bill from Vercel. This outraged many developers, so they started looking for cheaper alternatives that let them self-host their deployments.

Alternatives such as Heroku were previously and widely favored among developers due to its user-friendly nature and the provision of a free hobbyist tier. However, since Heroku discontinued its free plans, it’s no longer the go-to choice for most developers.

One alternative that has recently gained widespread adoption and popularity is Coolify, a free, open-source, self-hostable platform that provides the convenience of cloud services and enables users to host their applications effortlessly. With Coolify, you can avoid the risk of unforeseen charges, all while maintaining full control over your infrastructure.

In this article, I’ll list the benefits of using Coolify, walk you through the process of setting up a server and hosting Coolify, and demonstrate how to deploy resources on it.

If you’re already familiar with Coolify and are looking to implement it, then you can go ahead and skip to the getting started section.

What to know before implementing Coolify

This article assumes you have the following:

  • Basic knowledge of React and Express
  • Node.js and Git installed on your computer

Coolify as an IaC solution

Managing infrastructure manually is no longer feasible in today's fast-paced development environment. As projects scale and evolve, maintaining consistency, reducing human error, and ensuring efficient deployment processes become paramount. This is where Infrastructure as Code (IaC) comes into play.

IaC is a DevOps practice that manages infrastructure using code. While this explains what IaC is, understanding its importance requires knowing why it’s needed and what problems it solves in relation to Coolify’s purpose.

Before IaC, IT infrastructure was managed manually. Developers or operations teams had to set up servers and configure them before deploying applications. This process was time-consuming and had issues like high cost, scalability problems, and inconsistencies.

The advent of Cloud computing relieved some of these discrepancies, allowing IT professionals to build and maintain data centers at lower costs. However, inconsistencies persisted when different people manually made configurations.

IaC revolutionizes how we handle infrastructure by enabling the management and provisioning of resources through code, fostering automation, version control, and collaboration. Tools like Terraform, Ansible, and Chef let developers define infrastructure (like servers and databases) in configuration files. This allows them to edit, copy, distribute, and repeat the setup without inconsistencies.

So, how does Coolify fit into all of this?

What is Coolify?

Coolify bridges the gap between traditional IaC tools and user-friendly application deployment. While tools like Terraform, Chef, and Ansible focus on managing and provisioning infrastructure through code, they can be complex and require significant expertise. Coolify, on the other hand, offers a simpler, more accessible interface for deploying and managing applications, making it easier for developers who may not be experts in IaC to still benefit from its concepts.

In addition to aligning with IaC principles, Coolify offers several key features that embrace the IaC approach to modern development workflows:

  • Declarative Configuration — Coolify allows you to define the desired state of your applications and services through a web interface, which is a key principle of IaC
  • Version Control Integration — you can integrate Coolify with Git repositories, enabling you to store your application and infrastructure configurations as code. This supports the IaC principle of version-controlled configurations
  • Automated Deployments — Coolify automates the deployment process, ensuring consistency and repeatability, which are core benefits of IaC
  • Infrastructure Management — Coolify provides tools to manage and monitor your deployed services, similar to how IaC tools manage infrastructure states

Additionally, Coolify supports a Terraform provider, which enables you to manage its resources through Terraform code, thus bringing IaC capabilities into the mix.

Getting started with Coolify

To start using Coolify, you'll need a server. This can be a Virtual Private Server (VPS) or any server you can access remotely. The server will host Coolify, allowing you to manage your applications and services.

There are many VPS providers to choose from, such as Digital Ocean, Linode, and Hetzner. Your decision will likely depend on who offers the best price and performance.

Coolify recommends Hetzner for its affordable and high-performance servers. However, their account verification process can be challenging, often rejecting new accounts without clear reasons.

After several rejections, I chose to go with Digital Ocean, which also offers a good balance of cost and performance. If you think you can successfully register with Hetzner, please consider using the referral link in the Coolify documentation to support the project.

Setting up a VPS

To set up a VPS, start by creating an account with your preferred provider, and then create a server. Pay attention to these key details when configuring your server:

Region

Choose a server location close to your main users to ensure fast and reliable connections. For example, I chose London: Create Droplets page showing region selection options, including New York, San Francisco, London, Singapore, and others, with London selected.  

Operating system

Select an operating system for your server. Ubuntu is a popular choice, or you can use one recommended by Coolify: Interface for choosing an operating system image. The options include Ubuntu, Fedora, Debian, CentOS, AlmaLinux, and Rocky Linux. The selected image is Ubuntu 24.04 (LTS) x64.  

Hardware resources

Choose the amount of CPU, RAM, and storage your applications will need. Coolify requires at least 2 CPUs, 2GB memory, and 30GB of storage. For my example, I chose a server with 2 CPUs, 4GB RAM, and 80GB storage: Screenshot of DigitalOcean CPU options showing pricing for different droplet configurations. The selected option is  

Access method

Decide how you will access the server. You can use either SSH keys or a password. SSH keys are recommended for better security: A screen showing the option to choose an authentication method for a Droplet. The options include using an SSH key or a password. The SSH Key option is selected, and there is an option to add a public SSH key, with a note that SSH keys are more secure than passwords.   To set up SSH key access, click **Add SSH key** on your server setup page and a modal will pop up. I believe Hetzner has the same UX flow: A user interface for adding a public SSH key. The screen displays a large input box labeled   Next, open your terminal and run the following command to generate a key pair:



ssh-keygen


Enter fullscreen mode Exit fullscreen mode

If you're using Windows with Git installed, use the bash terminal to generate a key pair using the following command instead:



ssh-keygen -t rsa


Enter fullscreen mode Exit fullscreen mode

Alternatively, you can install Puttygen and follow this instruction on how to generate a key pair.

After running the command, you'll be asked to provide a path to save the key. Press enter to use the default path:



Generating public/private ed25519 key pair.
Enter file in which to save the key (/c/Users/david/.ssh/id_ed25519):


Enter fullscreen mode Exit fullscreen mode

Then, you'll be prompted to enter a passphrase. Choose a strong password and save it securely:



Enter passphrase (empty for no passphrase):
Enter same passphrase again:


Enter fullscreen mode Exit fullscreen mode

Once you've set the passphrase, you'll see a confirmation message indicating the SSH key pair was created:



Your identification has been saved in /c/Users/david/.ssh/id_rsa
Your public key has been saved in /c/Users/david/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:j9HUkzKvqJoxGQYXCwjAnYMu9c/0fabrYosgMrvk160 david@Enigmae
The key's randomart image is:
+---[RSA 3072]----+
|*.+ o                |
|.o.= o         . .   |
|....+         + +        |
|.. o. .  o + .   |
|.   o+ .S.. .        |
|   . oo .=..o        |
|o.. * . o o+         |
|o+ o * +o .          |
|oo. oE+..+o.         |
+----[SHA256]-----+


Enter fullscreen mode Exit fullscreen mode

Next, run the following command to display your public key:



cat ~/.ssh/id_rsa.pub


Enter fullscreen mode Exit fullscreen mode

The output should look something like this:



ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCjNZ2iUSZ6zS8MbQvF2CchdNxL52a0ovTKc2ikFAzIdOnVm+zt1r8k1+TEQaWAZqod1TZp21VdyQ9tdLwRRUDWwATBuyW+XGUlSzZU+EZuxKzno4V8R4X7qM57Of3r9U03v/PMv22z7k6aCdoEW/YNm0j+IzgN3nOL0zsWjkll79XKfJpUi+wvkmcm8D+4vQrXZyV46tilmLZPPek9T2fY/2HPtg4kuQKuJgTvAsWOLHdIRQuKCVhTeRgKy8ekxp+Q1T05DeopUHcz/uLg0kRbtc+SuvWqo0cQmwToTdjqBxic0sJBS3dYyhIim6I2BXYbC/6LrXMVXYrOPo9SqgXeiHKdvY+LY3dJIZCUFWMKi5JELaA3fm+hohwDe5+wtlMuP0jJLk+YTIbrDNcRWohQRmsbHXOgGHGraXxM8t6IcG55Uc4uNXysmriZf0DYG58qMzdeWgBzps4AnMRGYk1Lrbg8hK38nzYDwvQWpIhckCm1VtkiB/yXcovBSIUH+8U= david@Enigmae


Enter fullscreen mode Exit fullscreen mode

Copy the output and paste it into the modal on your server setup page, then click Save or Add SSH Key: Screenshot of the  

After completing these steps, review your server's configuration. If everything looks good, click the create button to create your server: Screenshot showing the pricing for a droplet on a cloud platform. The price is $24.00 per month or $0.036 per hour. There are two buttons: one labeled  

Installing Coolify

Once your virtual server is up and running, open your terminal and use this command to access it via SSH:



ssh root@<server-ip-address>


Enter fullscreen mode Exit fullscreen mode

Replace <server-ip-address> with your server's IP address, which you can find here: Screenshot of the Coolify_server Web Application page. The Resources tab is selected, showing a droplet named  

After running the command, you'll be asked if you want to add the IP address to the list of known IPs. Type "yes" and press enter:



$ ssh root@134.122.96.44
The authenticity of host '134.122.96.44 (134.122.96.44)' can't be established.
ED25519 key fingerprint is SHA256:yfaoc/1er+eRO404gd83jRqQfh0Vo+hME1ONgYMasqA.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes


Enter fullscreen mode Exit fullscreen mode

The next prompt will ask you to enter the passphrase of your SSH key or your server's password if you used password authentication:



Enter passphrase for key '/Users/david/.ssh/id_rsa':


Enter fullscreen mode Exit fullscreen mode

Once authenticated, you will have remote access to your server: Terminal screenshot displaying system information for Ubuntu 24.04 LTS, including system load, memory usage, processes, and network details. The system shows that security updates are available and provides links to Ubuntu documentation and management.  

With remote access, we can run commands on the remote server directly from our computer. Let's install Coolify on the server. Paste the following command into your terminal and press enter to start the installation:



curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash


Enter fullscreen mode Exit fullscreen mode

The installation process takes one to two minutes. Do not cancel it.

Once completed, the terminal will show an IP address where you can access Coolify. In my case, it was: http://134.122.96.44:8000: Terminal screenshot showing the output of starting various Coolify containers, such as coolify-redis and coolify-db, with statuses like  

Copy the IP address and enter it into your browser. This will take you to a registration page where you register to access the Coolify instance: Coolify registration page with fields for name, email, password, and a button to register or log in if already registered.

After registering, you'll be redirected to a welcome page. Click Get Started to proceed through the onboarding pages: A welcome screen for the Coolify platform. The screen features a large, centered message saying  

Follow the onboarding steps until you reach the "Server" page, where you'll choose to deploy resources on Localhost or a Remote Server: Screenshot of the server deployment options in Coolify, asking whether to deploy resources to Localhost or a Remote Server.  
Remember, resources are components like your application, database, server, or other services you want to set up on your server. Here, Coolify is asking if you want to set these up on the same server where Coolify is installed (i.e. Localhost) or on a different remote server. If you don't have another remote server, choose Localhost and create a new project: Screenshot of the Coolify interface prompting the user to create a new project. The text reads

After clicking Create new project, you'll finish onboarding. Click Let's do it! to access your Coolify dashboard: Screenshot of the Coolify dashboard showing an overview of the self-hosted infrastructure, including one project and one server. The dashboard also includes options for settings, resource addition, and deployment management.  

Et Voila! Coolify is now installed on your VPS. We’ll create and deploy a sample application in the next section.

Creating a sample application

To demonstrate deploying resources on Coolify, we’ll create the quintessential to-do application that features creating, reading, updating, and deleting (CRUD) tasks.

Ideally, we would use Next.js to deploy the server and frontend together. However, to demonstrate how to deploy standalone resources, we’ll deploy each one individually: a frontend (Next.js), a server (Node.js), and a database (Postgres).

If you already have a project or want to go straight to deploying resources, you can skip this section.

Creating the Node.js server

Let's start with the Node.js server. Create a folder on your computer, open a terminal in that folder, and run this command:



mkdir backend && cd backend


Enter fullscreen mode Exit fullscreen mode

This command will create a "backend" folder and navigate into it.

Next, initialize a new Node project and install the required dependencies:



npm init -y
npm install express pg cors body-parser dotenv


Enter fullscreen mode Exit fullscreen mode

After installing the dependencies, set up the project with this structure:



backend/
├── controllers/
│   └── tasksController.js
├── db/
│   └── db.js
├── routes/
│   └── tasks.js
├── index.js
└── package.json


Enter fullscreen mode Exit fullscreen mode

With the project structure ready, we can start building the app's business logic. Start by opening the db.js file in the db folder and adding the following code:



// db/db.js
const { Pool } = require('pg');

const pool = new Pool({
  user: 'postgres',
  host: 'localhost',
  database: 'todos',
  password: 'pass',
  port: 5432,
});

const query = (text, params) => pool.query(text, params);

module.exports = {
  query,
};


Enter fullscreen mode Exit fullscreen mode

This code establishes a connection pool to a PostgreSQL database and provides a function for executing SQL queries.

Leave the db.js file for now, and open the tasksController.js file in the controllers folder. Add this code:



// controllers/tasksController.js
const db = require("../db/db");

const getTasks = async (req, res) => {
  try {
    const result = await db.query("SELECT * FROM todos ORDER BY id");
    res.json(result.rows);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
};

const createTask = async (req, res) => {
  const { title, description } = req.body;
  try {
    const result = await db.query(
      "INSERT INTO todos (title, description) VALUES ($1, $2) RETURNING *",
      [title, description]
    );
    res.json(result.rows[0]);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
};

const updateTask = async (req, res) => {
  const { id } = req.params;
  const { title, description, completed } = req.body;
  try {
    const result = await db.query(
      "UPDATE todos SET title = $1, description = $2, completed = $3 WHERE id = $4 RETURNING *",
      [title, description, completed, id]
    );
    res.json(result.rows[0]);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
};

const deleteTask = async (req, res) => {
  const { id } = req.params;
  try {
    await db.query("DELETE FROM todos WHERE id = $1", [id]);
    res.sendStatus(204);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
};

module.exports = {
  getTasks,
  createTask,
  updateTask,
  deleteTask,
};


Enter fullscreen mode Exit fullscreen mode

This code creates controller functions to interact with the Postgres database using prepared statements to:

  • Retrieve all tasks (getTasks)
  • Create a new task (createTask)
  • Update an existing task (updateTask)
  • Delete a task (deleteTask)

Next, add routes for the controller methods by opening the tasks.js file in the routes folder and adding this code:



// routes/tasks.js
const express = require('express');
const router = express.Router();
const tasksController = require('../controllers/tasksController');

router.get('/', tasksController.getTasks);
router.post('/', tasksController.createTask);
router.put('/:id', tasksController.updateTask);
router.delete('/:id', tasksController.deleteTask);

module.exports = router;


Enter fullscreen mode Exit fullscreen mode

Finally, open the index.js file and add this code:



// index.js
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const tasksRoutes = require('./routes/tasks');

const app = express();
const port = process.env.PORT || 5000;

app.use(cors());
app.use(bodyParser.json());

app.use('/tasks', tasksRoutes);

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});


Enter fullscreen mode Exit fullscreen mode

Our server is halfway done. We just need to set up and connect to a Postgres database to make it functional.

Creating a Postgres database

Setting up a Postgres database on Coolify is fairly straightforward because Coolify offers a ton of one-click resources for instant deployment, with Postgres being one of them.

First, log into your Coolify dashboard and click add resource next to the project name: Screenshot of the Coolify dashboard showing the  

This will take you to a page with various templates. Scroll down and select Postgres: Coolify Projects page displaying various database options such as PostgreSQL, Redis, DragonFly, KeyDB, Clickhouse, MongoDB, MySQL, and MariaDB.  

On the configuration page, enter a name for the database and a password (or use the default). Click the start button at the top right to launch the database: Screenshot of the PostgreSQL database configuration page with a red status indicator showing the database has exited, and an arrow pointing to the start button.  

Once the database is up and running, scroll down and check the make it publicly available box. This provides a public connection URL for connecting to the database remotely: CoolifyDB interface showing the option to make the PostgreSQL URL publicly available with a checkbox and a public port set to 5432.  

Next, install a Postgres client like pgAdmin or DBeaver on your computer. Open it, click add new server, and a modal will appear: Register Server dialog box in a database management tool with an error message indicating that either Host name or Service must be specified.  

Add any name of your choice the server, click the connection tab, and enter the database host address. You can find this between @ and : in the public connection string: Screenshot showing the Postgres URL details with both internal and public URLs. The internal URL is obscured, while the public URL is partially visible, highlighting the IP address and port used for the database connection.  

Then, enter your Postgres instance password in the password field, and click on the save button: Connection tab in the Register Server dialog box, showing the fields for entering the host name, port, maintenance database, username, and password for connecting to a PostgreSQL server.  

If successful, you’ll see the following dashboard, confirming that pgAdmin is successfully connected to the remote database: CoolifyDB dashboard displaying system statistics, including server sessions, transactions per second, tuples in and out, and block I/O performance metrics.  

Creating a table

The next step is to create a table for our data. To do this, click the arrow icon next to the server name on the sidebar. Click Databases, select the database name (e.g., Postgres), and click the query tool icon on the top bar: The interface shows a PostgreSQL management dashboard with various panels. On the left side, a navigation tree displays the hierarchy of objects within a database named  

This opens the query page where you can create a table using query scripts: Screenshot showing the pgAdmin interface connected to a remote PostgreSQL database, highlighting the query tool and available database objects in the navigation pane.  

Copy and paste the query script below into the field, and click the play icon to run it:



CREATE TABLE tasks (
  id SERIAL PRIMARY KEY,
  title VARCHAR(255) NOT NULL,
  description TEXT,
  completed BOOLEAN DEFAULT FALSE
);


Enter fullscreen mode Exit fullscreen mode

A screenshot of a SQL query editor connected to a PostgreSQL database named

After running the script, you should get a similar response as below which means the table has been successfully created:



Query returned successfully in 252 msec.


Enter fullscreen mode Exit fullscreen mode

Connecting the server and database

To connect the Node server to the database, create a .env file in the Node server’s root directory, add the following code to the file, and replace the long string with your database’s password:



>DB_PASSWORD="aIxEt6GJPu1DUjgKTT1g5VJv1jp25X3qYduFqI0zCGB04UnzcQfthgcutDmJPWFf"


Enter fullscreen mode Exit fullscreen mode

Then, update the db.js file with your database credentials and port like so:



const { Pool } = require("pg");

const pool = new Pool({
  user: "postgres",
  host: "134.122.96.44",
  database: "postgres",
  password: "aIxEt6GJPu1DUjgKTT1g5VJv1jp25X3qYduFqI0zCGB04UnzcQfthgcutDmJPWFf",
  port: 5432,
});

const query = (text, params) => pool.query(text, params);

module.exports = {
  query,
};


Enter fullscreen mode Exit fullscreen mode

We updated the host, database, and password fields with the database’s hostname, database name, and password, respectively.

You can find the database name in the public connection URL string we exposed earlier. It appears after the last forward slash in the URL. For example, postgres, which is after the last forward slash, is the database name in the example below:



postgres://postgres:aIxEt6GJPu1DUjgKTT1g5VJv1jp25X3qYduFqI0zCGB04UnzcQfthgcutDmJPWFf@209.97.189.179:5432/postgres


Enter fullscreen mode Exit fullscreen mode

The database password can also be found on the configuration page: Coolify interface displaying the PostgreSQL database configuration settings, including fields for database name, username, password, and network configurations.  

Now, start the development server to verify if it successfully connects to the database: Screenshot of a POST request made in Postman to the endpoint http://localhost:5000/tasks with a JSON body containing  

Creating a Next.js application

Setting up the frontend application is straightforward, so we won’t spend too much time on it.

Open your terminal and navigate to the “frontend” directory. Run the following command to install Next.js:



npx create-next-app@latest


Enter fullscreen mode Exit fullscreen mode

After the installation, run the following commands to install Axios, and start the development server:



npm i axios && npm run dev


Enter fullscreen mode Exit fullscreen mode

Next, open the page.js file in the app folder, clean up the boilerplate code, and add the following code:



import React, { useState, useEffect } from "react";
import axios from "axios";

const App = () => {
  const [tasks, setTasks] = useState([]);
  const [title, setTitle] = useState("");
  const [description, setDescription] = useState("");

  useEffect(() => {
    fetchTasks();
  }, []);

  const fetchTasks = async () => {
    const res = await axios.get("http://localhost:5000/tasks");
    setTasks(res.data);
  };

  const addTask = async () => {
    const res = await axios.post("http://localhost:5000/tasks", {
      title,
      description,
    });
    setTasks([...tasks, res.data]);
    setTitle("");
    setDescription("");
  };

  const updateTask = async (id, completed, title, description) => {
    const res = await axios.put(
      `http://vow8owk.209.97.189.179.sslip.io/tasks/${id}`,
      {
        title,
        description,
        completed,
      }
    );
    setTasks(tasks.map((task) => (task.id === id ? res.data : task)));
  };

  const deleteTask = async (id) => {
    await axios.delete(`http://localhost:5000/tasks/${id}`);
    setTasks(tasks.filter((task) => task.id !== id));
  };

  return (
    <div>
      <h1>Add a task...</h1>
      <div>
        <input
          type="text"
          value={title}
          placeholder="Title"
          onChange={(e) => setTitle(e.target.value)}
        />
        <textarea
          cols="45"
          rows="8"
          placeholder="Description"
          onChange={(e) => setDescription(e.target.value)}
        />
        <br />
        <button onClick={addTask}>Add Task</button>
      </div>
    </div> {/* Close the div properly */}
  );
}; 
export default App;


Enter fullscreen mode Exit fullscreen mode

Here, we use Axios to interact with the database through the endpoints provided by our Node server.

Lastly, add the specified styles to the CSS file and go to localhost:3000 in your browser:



@import url('https://fonts.googleapis.com/css2?family=Nanum+Pen+Script&display=swap');

:root {
  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
  line-height: 1.5;
  font-weight: 400;

  color-scheme: light dark;
  color: #111c25;
  background-color: #ffffff;

  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

body {
  margin: 0;
  min-height: 100vh;
  color: #111c25;
  background-color: #ffffff;
}

h1 {
  font-size: 3.2em;
  line-height: 1.1;
}

button {
  border-radius: 8px;
  border: 1px solid transparent;
  padding: 0.6em 1.2em;
  font-size: 1em;
  font-weight: 500;
  font-family: inherit;
  background-color: #646cff;
  cursor: pointer;
  transition: border-color 0.25s;
}

li {
  list-style-type: none;
}

ul {
  padding: 0 15px;
}

p,
h1 {
  margin: 0;
}

button:hover {
  background-color: #d80b0b;
}

.add-task-button:hover {
  background-color: #414491;
}

button:focus,
button:focus-visible {
  outline: 4px auto -webkit-focus-ring-color;
}

input[type=text] {
  max-width: 100%;
  border-radius: 5px;
  background-color: white;
  color: #1a1a1a;
  border: solid #0060DF 2px;
  padding: 10px;
  margin-bottom: 10px;
}

textarea {
  border-radius: 5px;
  background-color: white;
  border: solid #0060DF 2px;
  padding: 10px;
  max-width: 100%;
  margin-bottom: 10px;
  color: #1a1a1a;
}

.task-form {
  display: flex;
  flex-direction: column;
  max-width: 400px;
}

.app-container {
  width: 100%;
  display: flex;
  justify-content: space-around;
  align-items: center;
}

.task-card {
  border: solid #0060DF 2px;
  border-radius: 5px;
  padding: 10px;
  width: 400px;
  margin-bottom: 10px;
}

.task-title {
  margin-bottom: 5px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

h1 {
  margin-bottom: 60px;
  font-family: "Nanum Pen Script", cursive;
}

.task-list-header {
  text-align: center;
  color: rgb(96, 96, 96);
  font-family: "Nanum Pen Script", cursive;
  font-size: 30px;
}

.task-checkbox {
  width: 15px;
  height: 15px;
  background-color: #f0f0f0;
  border: 1px solid #ccc;
  border-radius: 15px;
}

.task-completed {
  opacity: 0.5;
}

.delete-task-button {
  opacity: 1;
  background: #d80b0b;
  margin-top: 10px;
  display: block;
}

.task-list-section {
  border: #0060DF 0.2px solid;
  height: 700px;
  overflow-y: scroll;
  padding: 0 20px;
}

.task-actions {
  position: relative;
}

.arrow-icon {
  position: absolute;
  top: -30px;
  left: -50px;
  rotate: -30deg;
}

span {
  position: absolute;
  top: -45%;
  left: -100px;
  rotate: -40deg;
  font-size: 17px;
  font-family: "Nanum Pen Script", cursive;
  color: gray;
}

.show-text{
  display: flex;
  font-family: "Nanum Pen Script", cursive;
  justify-content: center;
  align-items: center;
  font-size: 40px;
  color: gray;
  width: 100%;
  height: 500px;
}

.hide-text{
  display: none;
}

@media only screen and (max-width: 868px) {
  .app-container {
    flex-direction: column;
    overflow: hidden;
  }

  .task-list-section {
    margin-top: 50px;
    border: none;
    border-top: 2px solid #747bff;
  }

  .task-card {
    width: 400px;
  }
}


Enter fullscreen mode Exit fullscreen mode

If everything is set up correctly, the application should work as shown in the GIF below: A user interface screen for adding a task in a task management application. On the left side, there is a form titled  

That's it! All three resources are working together and running smoothly. You can find the code for the NextJs and Node server in my GitHub repository.

The next step is to deploy both the Node server and React application to our Coolify instance.

Deploying resources on Coolify

Coolify supports git-based deployment using Nixpacks, an open-source Buildpack tool that creates container images from a source directory for deployment anywhere on the web.

In other words, we don't have to go through the trouble of containerizing our resources before deploying them on Coolify — we simply need to create and push our resources to their respective Git repositories, and Coolify will handle the rest.

Go ahead and push the Node server and Next.js app to their respective repositories Then, in your Coolify dashboard, click the add resource button and select the Public Repository option: A screen showing the  

Return to the Node server’s repository, copy the repository link from the address bar, and paste it into the field on Coolify: Screenshot of the  

Once Coolify loads the repository, choose Nixpacks as the buildpack and set the port to 3000. Then click Continue: A screenshot of a form used to create a new application from a public Git repository. The form includes fields for entering the Repository URL, Rate Limit, Branch, Port, and selecting a Build Pack. The URL provided points to a GitHub repository for a Coolify Node server. The Branch is set to  

On the next page, navigate to Environment > +New, and add the environment variable for the database password. Click save: A screen showing the creation of a new environment variable. The variable name is  

After saving the environment variables, click Deploy at the top right corner of the screen to deploy the resource.

To monitor the deployment process, click the show debug button for detailed logs: Deployment log screen showing the progress of a Docker image build. The log includes details about the Nixpacks version used, the setup commands, and the installation of npm packages. A red arrow points to the  

Once the application is successfully deployed, click the Links tab to find the domain and see your app live: Screenshot of the configuration page for a Coolify Node.js server. The page shows the server is running, with options to redeploy, restart, or stop the server. The server's URL is displayed in a purple box.  

Repeat the same process to deploy the Next.js application, but remember to use the database’s private (internal) connection URL and uncheck the make it publicly available checkbox. This ensures secure internal communication within your Coolify setup: Screenshot of the PostgreSQL configuration interface showing the internal Postgres URL, public port settings, and an option to make the port publicly available.  

If everything is set up correctly, all three services should be running as expected: Screenshot of the Coolify resources dashboard showing deployed resources including a Next.js app, Node server, and PostgreSQL database in a production environment.  

Note that the connection to the application is not secure yet. Normally, this would be addressed with a custom domain, but for now, you can enable a free, temporary SSL certificate for the generated domain by adding an s after http in the address on the configuration page, like this: Configuration screen showing the domain settings for a  

Managing Coolify resources

When deploying for production, it’s important to be able to make changes to our resources, such as upgrading to a newer version or switching to a different version. Coolify makes this easy.

Upgrading resources like the Node server and the Next.js application is as simple as pushing the update to the source repository and redeploying the instance on Coolify.

Manually doing this back and forth can quickly become tedious. Thankfully, Coolify allows us to automate deployments with webhooks. This means Coolify will automatically redeploy a resource if it detects new updates in the project's repository.

To set this up, go to your resource’s webhook menu. You can find this on the sidebar of your resource’s configuration page.

In the webhooks menu, click on the webhook configuration on GitHub button: Screenshot of the GitHub App registration page in Coolify. The image shows a warning message stating  

Change the webhook endpoint to your Coolify instance’s custom domain, if you have one set up, to avoid exposing your server IP, as shown in the image above.

Next, click the Register Now button to log into your GitHub account, authenticate the GitHub app, and give it access to your repositories: Installation screen for a GitHub app named  

Make sure to select All repositories and click the install button.

After the installation, you’ll be redirected back to the GitHub app page, where you’ll see the information needed to trigger a rebuild when GitHub detects a new update: GitHub App settings screen for configuring a private GitHub App for repositories. The screen includes fields for App Name, Organization, API URL, User, and several secret credentials such as Client ID, Client Secret, and Webhook Secret. Permissions settings and update options are also visible.  

Everything is hooked up! If we go back to the Coolify dashboard, we should be able to create new resources from our newly added source.

The process is similar to how we deployed the initial resources. The only difference is that instead of choosing the public Repository option on the “New Resources” page, we choose the Private Repository (with GitHub app) option: Screenshot of the  

On the next page, choose the GitHub app we just created, and then select the repository you wish to load from the dropdown menu: Screen for creating a new application by selecting a GitHub repository. The interface shows a dropdown list of available repositories, with one repository currently highlighted. Options to add a GitHub App or change repositories on GitHub are also available at the top of the screen.  

This will take you to a familiar page — the deployment configuration page. Click the deploy button to complete the process and deploy the application. Now, if you push an update to that repository, Coolify will be notified and automatically redeploy the resource.

Conclusion

So, is Coolify the ultimate alternative to the status quo? This depends on your requirements and the vision you have for your application. If you see your application scaling significantly in the future, setting up a self-hosted deployment might be a worthwhile investment. Otherwise, solutions like Heroku and Vercel will likely suffice.

While this guide will get you started with Coolify, there are even more features that I didn’t get into, such as setting up custom domains with wildcards for your Coolify instance and deployed resources or how to open up and close ports using a firewall.

What’s something you’d like to see with Coolify? Let me know in the comments. Thanks for reading, and happy building.


200s only ✔️ Monitor failed and slow network requests in production

Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If you’re interested in ensuring requests to the backend or third-party services are successful, try LogRocket.

LogRocket Signup

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens while a user interacts with your app. Instead of guessing why problems happen, you can aggregate and report on problematic network requests to quickly understand the root cause.

LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. Start monitoring for free.

Top comments (0)