When I first went to set up Coolify, I found it very surprising that all the guides/videos I came across only had instructions to set up the Coolify dashboard on the same server as the sites/services it'll be hosting. This seemed strange to me considering it says right on the onboarding page:
"It is not recommended to use one server for everything."
So for this guide we'll be setting up the Coolify instance on an LXC in a self hosted Proxmox node and then setting it up to deploy to our VPS. This way the VPS can simply act as a web server and Coolify can be cool.
One common plague with running a VPS is getting slammed by bots hitting your SSH port trying to get a quick win. In this guide we'll be using Tailscale SSH which will allow us to turn off SSH on the VPS completely. We'll only open the ports needed to host our services.
One thing to note: Unfortunately, this guide will set everything up on the root account on all the servers. Coolify claims you can manage as a non-root user, but for the life of me I couldn't figure out how to get it working. It's not the end of the world since the only public facing server is the VPS and we'll have our ports locked down, but still. I just wanted to mention that before starting. If you're reading this and managed to get everything working on a non-root account, please hit me up either on Github, Gitlab or Reddit so we can talk.
Note: This post got pretty lengthy so I split the section on setting up a build server to here
If you want to follow along, these are the things we'll be using:
- Proxmox node where we will create the LXC to host Coolify
- VPS that will be our web server.
- Tailscale account
- Domain name (optional)
- Cloudflare account to manage DNS (optional)
There's a lot of steps to setting this up, but I'll try to make it as simple as possible. And if you've read any of my other posts you may have realized I'm not a fan of bothering with screenshots. Just dense text ready to copy and paste.
Let's get started...
VPS
It doesn't really matter where your VPS is hosted, just as long as it has enough resources to handle whatever you're hosting and you can log in as root.
I'll be using a VPS with a fresh install of Debian 12 (bookworm).
SSH in to the VPS as root
Update the system
Reboot
Install some quality of life packages
These are just the basic packages I always install when setting up a server. The important ones here are git, ufw and curl. If nothing else, install those.
If you installed ZSH like in the command above, install oh-my-zsh now
When it asks if you want to make ZSH the default shell, type y
.
Logout
Reconnect to the VPS via SSH
Your default shell should be zsh now. You can check by running
And you should see something like /usr/bin/zsh
Lets get this thing on Tailscale so we can turn on the firewall and lock it down.
Tailscale
- Log in to your Tailscale account and click the "Add device +" button within the Machines section and select Linux server.
- You can keep the default settings and click the Generate install script button at the bottom.
- Copy the code that it gives you and paste it in the VPS terminal.
Your VPS should now be connected to your tailnet and see the VPS hostname in your Tailscale dashboard under the Machines section.
Restart Tailscale with the ssh flag
The Machines section on the dashboard should now show a little "SSH" tag next to the hostname.
Make sure the computer you're using to log in to the VPS (we'll call this "local computer") is on the same tailnet because we'll be logging in via Tailscale SSH from now on.
Local computer
Close the connection to the VPS and re-login, but instead of using the VPS IP address, use the tailnet IP of the VPS from the Tailscale dashboard
Tailscale has a thing where if you're logging in as root it will make you verify your account. It resets every 12 hours by default, but you can change this in your ACL rules on the Tailscale dashboard. I personally don't really mind, but you can make that change if you want.
You should now be logged in to the VPS via the tailnet IP address. Now we'll add some firewall rules.
Note: Again, make sure you are connected via the tailnet IP address. We will be closing the SSH ports and you may block yourself from logging in. If this does happen, before you wipe out the machine and start over, check to see if your VPS provider offers a VNC option where you can connect via a web-shell. Then you can disable the firewall rules and fix the issue.
VPS
Let's set some firewall rules using UFW
Note: The rules will not be applied until the end, so don't worry if you make a mistake right now.
First we'll disable all incoming and outgoing traffic. We do this so by default, everything is blocked and we only allow the specific traffic we need.
HTTP
HTTPS
DNS
Once we set up the Coolify LXC we'll have to add that to the rules, but for now this should be fine.
And once we know everything is set up and working we'll come back and turn off SSH and set some ACL rules in our Tailscale config.
Turn on UFW
You should still be connected to the VPS at this point.
You can check the UFW rules by typing
If you need to remove a rule you can do
Make sure to reload UFW after updating the rules
Proxmox
Now we'll log in to our Proxmox dashboard and make the LXC for our Coolify instance. Just FYI, I'm running Proxmox v7.4-17 on this instance. I doubt it matters much for what we're doing, but just in case.
Download the Debian 12 LXC image by selecting your storage device, go to the CT Templates option and clicking the Templates button at the top.
Choose the Debian 12 image and download it.
Once it's done, click the Create CT button at the top of the screen. I set my container up like this, but you can customize yours however you like.
Don't start the container yet because we need to update the config file on the main Proxmox node to pass Tailscale stuff. For this you'll need to select your Proxmox node and open the shell. Or SSH in, whatever.
Inside the main Proxmox node shell, edit the following file
Add the following to the end of the file
This will let us log in to the unprivileged container via Tailscale SSH.
Save the file and exit the shell.
Start the new Coolify LXC
Coolify LXC
Log in as root
Update the system
Reboot
Log back in
Install the same "QOL" packages as before
If you installed ZSH like in the command above, install oh-my-zsh now
Reboot
Tailscale dashboard
Click the Add machine + button and generate a script for the Coolify LXC
Coolify LXC
Paste the script in to the Coolify LXC to install Tailscale
Start Tailscale with SSH
Test that it works by pinging the VPS with the tailnet IP
If you got a response, try to SSH in to the VPS with the tailnet IP
You should now be logged in as root on the VPS via Tailscale SSH on the Coolify LXC container. Great stuff. We are doing things.
VPS
Add the Coolify LXC to the UFW rules
Reload UFW
Coolify LXC
Now we can finally install Coolify and get that set up
Install Coolify
Note: It kinda hangs toward the end, but it's finished when you see
Local computer (Coolify dashboard)
Log in to the Coolify Dashboard by going to http://<coolify-lxc-ip>:8000
Create an admin user and start the onboarding process.
On the Server step, select Remote Server
When it asks about SSH keys, select No (Create one for me) and update the name to something like "Coolify LXC" or whatever. It doesn't really matter.
Give the server a name and put in the Tailnet IP address of the VPS
Click Next and validate the server.
If it worked you should see a bunch of green checkmarks and it will install some Docker stuff on your VPS. If you get an error, log in to the VPS and disable UFW and try to verify again. Chances are the issue is something with the firewall rules. Narrow down what the problem could be and continue once you're able to verify the VPS as a server in Coolify with the firewall enabled.
We've now got Coolify running locally on an LXC in a self-hosted Proxmox instance connected to a VPS via an encrypted tunnel with Tailscale. Life is good.
Cloudflare/Domain provider
This step is optional, but I haven't had much luck using the auto-generated URLs provided by Coolify and this worked for me. Plus, having the domain protected by Cloudflare is something I would do anyway, win-win.
Just FYI, I use Porkbun for my domain provider and Cloudflare for the DNS/everything else, but you can use whatever domain provider you're comfortable with.
Log in to Cloudflare and import your domain by clicking the "+ Add a domain" button at the top of the Overview page
Type in your domain name, leave "Quick scan..." selected and Continue
Cloudflare will ask you to update your nameservers so go back to your domain provider and do that.
Wait a bit for the nameservers to switch over
Once Cloudflare is controlling your DNS, Go to the Account Home page, select your domain and click the DNS Records link at the top right of the page.
Change the A
record to the IP address of your VPS. Not the tailnet IP, just the regular VPS IP given to you by the VPS provider.
At this stage I also added a sub-domain that I'll use for testing stuff out. Do this by adding a CNAME
with whatever you want your sub-domain to be and set the value field to your domain. I'll add blog
as the sub-domain.
Save your settings and wait a minute.
Coolify dashboard
Let's test it out. I'm going to use the template for this blog as an example. If you want to do the same for the sake of testing or if you just want a sweet new self-hosted blog on your server, the repo is here
On the left-hand menu, select the Projects tab and click the + Add button at the top
Give the project a name.
Mine is called "pywkt-blog"
Coolify will automatically put it in the Production environment which is fine. Just click Production and click the Add New Resource button.
Select Public Repository
Select the VPS server
Enter the URL of the repository. (https://github.com/pywkt/pywkt-blog-template) and click the Check Repository button.
When the options appear:
Change the Publish Directory to /out
And check the box that says Is it a static site? because yes, this is a static site.
Click Continue
If you made a sub-domain in Cloudflare, set the Domains textfield to your sub-domain. eg: https://blog.pywkt.com
For whatever reason the Publish Directory value didn't save, so I'll change that to /out
again.
Make sure "Is it a static site?" is still checked.
Click the Save button at the top of the form.
And now the part that had me stuck for wayyyyy too long.
Select the Environment Variables option on the left side and click the Developer View button
Add the following to the Production section:
Save the variable and go back to the Normal View
Check the box that says Build Variable
Save and go back to the Configuration tab
If everything looks good, click the Deploy button at the top right of the form.
The app should build and once it finishes you should be able to navigate to the sub-domain/auto-generated domain and see the site.
Now that we know everything is connected and working as we would expect, let's go back on the VPS and disable SSH completely and set some ACL rules in Tailscale.
VPS
Check to see that the SSH service is running
If you see Active: active
, then we can run the following to turn it off.
Stop the service
Disable it from starting at boot
Set ACL Rules in Tailscale
Log in to Tailscale and go to the Access controls section at the top.
Change the example group to a "coolify" group
The <your-tailscale-user>
is the "username" located under the Tailscale node on the Machines page.
Add a tagOwners
section
Save the config and go to the Machines page
Click the three dots on the row for your VPS and select Edit ACL Tags
Select coolify from the dropdown that says Add tags and Save
Do the same thing for the coolify LXC node
Click the three dots next to your local machine and add the tag admin
Now go back to the Access controls section and inside the "acls" array, update it to the following
Note: Setting these rules will only allow the users in the
admin
group to have full access to the tailnet. If you need to access other nodes that are owned by another user, you will either need to tag them appropriately or define more rules.
Now add the following to the "ssh" array in the ACL
These rules will allow admin
and coolify
to SSH in to the VPS and Coolify LXC as root
and any non-root user.
Save and test the connections
With the previous rules, nodes tagged coolify
will only be able to access other nodes with the same tag.
Now let's set up UFW on our Coolify LXC and block it from the rest of our local network since we've got everything already connected with Tailscale
Coolify LXC
SSH in to the Coolify LXC from your local machine and make sure Tailscale is running so you're using Tailscale SSH
Install UFW
Disable everything by default
Attempting to ping any IP should fail at this point
Allow Tailscale
Allow localhost so the Coolify dashboard can do it's thing
You should now be allowed to ping the VPS since it's got Tailscale SSH running and it has been added to the coolify
group in our ACL file.
The LXC should also be blocked from accessing any other machines on the local network which is good too. Imagine a "worst-case-scenario" where your VPS gets compromised: The "bad actor" has control of your VPS which is able to communicate with your Coolify LXC via Tailscale, but Tailscale SSH has a 12 hour timeout before it needs to be validated again so that's a pretty good blocker right there. If they somehow managed to get past the Tailscale verification and authenticate themselves, the VPS only has access to the Coolify LXC and the Coolify LXC is cut off from everything.
Well that was a bit much, aaaaand the way it's currently running is fiiine, but it could be better...
How about instead of building the Docker image on our VPS we set up a build server on our Proxmox instance and build everything locally and then just push it to the VPS after? Yeah, that sounds like a pretty good idea...