Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da76e39ce3 | ||
|
|
b9e736cd80 |
@@ -0,0 +1,61 @@
|
||||
name: Deploy Coolify to VPS
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
# Inject secrets directly into Terraform via TF_VAR_ environment variables
|
||||
TF_VAR_ovh_endpoint: "ovh-eu"
|
||||
TF_VAR_ovh_application_key: ${{ secrets.OVH_APPLICATION_KEY }}
|
||||
TF_VAR_ovh_application_secret: ${{ secrets.OVH_APPLICATION_SECRET }}
|
||||
TF_VAR_ovh_consumer_key: ${{ secrets.OVH_CONSUMER_KEY }}
|
||||
TF_VAR_ovh_service_name: ${{ secrets.OVH_SERVICE_NAME }}
|
||||
TF_VAR_ssh_public_key: ${{ secrets.SSH_PUBLIC_KEY }}
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Terraform
|
||||
uses: hashicorp/setup-terraform@v2
|
||||
with:
|
||||
terraform_version: "1.5.0"
|
||||
|
||||
- name: Terraform Init
|
||||
run: |
|
||||
terraform init \
|
||||
-backend-config="address=${{ github.server_url }}/api/v1/packages/${{ github.repository_owner }}/terraform/state/coolify" \
|
||||
-backend-config="username=${{ github.actor }}" \
|
||||
-backend-config="password=${{ secrets.GITHUB_TOKEN }}"
|
||||
working-directory: terraform
|
||||
|
||||
- name: Terraform Plan
|
||||
run: terraform plan
|
||||
working-directory: terraform
|
||||
|
||||
- name: Terraform Apply
|
||||
run: terraform apply -auto-approve
|
||||
working-directory: terraform
|
||||
|
||||
- name: Install Ansible
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ansible
|
||||
|
||||
- name: Configure SSH Private Key
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
|
||||
- name: Run Ansible Playbook
|
||||
run: |
|
||||
ansible-playbook playbook.yml --private-key ~/.ssh/id_rsa
|
||||
working-directory: ansible
|
||||
@@ -0,0 +1,163 @@
|
||||
# Deploy Coolify to OVH VPS with Terraform, Ansible, and Gitea Actions
|
||||
|
||||
This repository contains all the configuration needed to dynamically provision a virtual private server (VPS) on OVH Cloud using **Terraform**, configure the VPS and install **Coolify** using **Ansible**, and automate the entire workflow via **Gitea Actions** on every push to the `main` or `master` branches.
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Architecture & Pipeline Flow
|
||||
|
||||
The deployment workflow is fully automated and consists of the following steps:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Push to main/master] --> B[Gitea Actions Runner Starts]
|
||||
B --> C[Terraform Initializes & Applies]
|
||||
C -->|Creates SSH Key & Instance| D[OVH Public Cloud VPS]
|
||||
C -->|Generates| E[ansible/inventory.ini]
|
||||
B --> F[Ansible Environment Setup]
|
||||
F -->|Reads inventory.ini| G[Ansible Playbook Run]
|
||||
G -->|Configures Firewall & Installs| H[Coolify on VPS]
|
||||
H --> I[Ready to Use on Port 8000/80/443]
|
||||
```
|
||||
|
||||
1. **Gitea Actions** is triggered on a `push` to the default branch.
|
||||
2. **Terraform** provisions an OVH Public Cloud instance (acting as our VPS), registers the deployment public SSH key on the instance, and outputs the public IP address.
|
||||
3. **Terraform** dynamically creates the Ansible `inventory.ini` using the provisioned VPS public IP.
|
||||
4. **Ansible** waits for SSH to become ready, updates system packages, configures the `ufw` firewall (opening ports 22, 80, 443, 8000, 6001), and runs the official non-interactive Coolify installation script safely and idempotently.
|
||||
|
||||
---
|
||||
|
||||
## 📁 Repository Structure
|
||||
|
||||
```text
|
||||
├── .gitea/
|
||||
│ └── workflows/
|
||||
│ └── deploy.yml # Gitea Actions CI/CD workflow configuration
|
||||
├── ansible/
|
||||
│ ├── ansible.cfg # Ansible master configuration
|
||||
│ ├── inventory.tpl # Template file for dynamic inventory generation
|
||||
│ └── playbook.yml # Playbook to configure VPS and install Coolify
|
||||
├── terraform/
|
||||
│ ├── main.tf # Terraform core resources (OVH instance, SSH key)
|
||||
│ ├── outputs.tf # Outputs the VPS IP address and Instance ID
|
||||
│ ├── providers.tf # Configures the OVH Terraform Provider
|
||||
│ └── variables.tf # Input variables with sensible defaults
|
||||
├── .gitignore # Prevents secrets/state files from being committed
|
||||
└── README.md # This documentation file
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Prerequisites
|
||||
|
||||
To run this pipeline, you will need:
|
||||
|
||||
1. **An OVH Cloud Account** with an active **Public Cloud Project**.
|
||||
2. **OVH API Credentials** with permissions to manage Public Cloud resources.
|
||||
3. **A Gitea Instance** with Gitea Actions enabled and a runner registered.
|
||||
|
||||
### 🔑 Step 1: Generate OVH API Credentials
|
||||
|
||||
Go to [OVH API Keys Creation Page](https://eu.api.ovh.com/createToken/) (or the corresponding URL for your OVH region, e.g. CA or US) and create a new set of API keys. Give your token access to `/cloud/*` and `/me/*` paths.
|
||||
|
||||
Once created, you will receive:
|
||||
- `Application Key` (AK)
|
||||
- `Application Secret` (AS)
|
||||
- `Consumer Key` (CK)
|
||||
|
||||
You also need your **Public Cloud Project ID** (often called `Service Name`). This can be found on your OVH Public Cloud Dashboard (it is a string of letters and numbers like `sb123456-ovh`).
|
||||
|
||||
### 🗝️ Step 2: Generate an SSH Keypair
|
||||
|
||||
Generate an SSH keypair that Gitea Actions will use to authenticate with the new VPS:
|
||||
|
||||
```bash
|
||||
ssh-keygen -t ed25519 -f id_rsa -N "" -C "gitea-actions-coolify"
|
||||
```
|
||||
|
||||
This generates:
|
||||
- `id_rsa` (Private key, to be saved in Gitea secrets)
|
||||
- `id_rsa.pub` (Public key, to be saved in Gitea secrets)
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Step 3: Configure Gitea Secrets
|
||||
|
||||
In your Gitea repository, navigate to **Settings > Actions > Secrets** and add the following secrets:
|
||||
|
||||
| Secret Name | Description | Example / Format |
|
||||
| :--- | :--- | :--- |
|
||||
| `OVH_APPLICATION_KEY` | Your OVH API Application Key | `xxxxxx` |
|
||||
| `OVH_APPLICATION_SECRET` | Your OVH API Application Secret | `xxxxxx` |
|
||||
| `OVH_CONSUMER_KEY` | Your OVH API Consumer Key | `xxxxxx` |
|
||||
| `OVH_SERVICE_NAME` | Your OVH Public Cloud Project ID | `ab123456-ovh-eu-1` |
|
||||
| `SSH_PUBLIC_KEY` | Content of the generated `id_rsa.pub` | `ssh-ed25519 AAAAC3...` |
|
||||
| `SSH_PRIVATE_KEY` | Content of the generated `id_rsa` | `-----BEGIN OPENSSH PRIVATE KEY-----...` |
|
||||
|
||||
---
|
||||
|
||||
## 💻 Running Terraform Locally
|
||||
|
||||
If you want to run Terraform locally to inspect, plan, or deploy from your own machine, you can do so while keeping the state synchronized with your Gitea Actions pipeline.
|
||||
|
||||
### Step 1: Install Terraform
|
||||
Make sure you have the Terraform CLI installed (`>= 1.3.0`).
|
||||
|
||||
### Step 2: Configure Local Credentials
|
||||
Create a file named `terraform.tfvars` inside the `terraform/` directory. *(Note: This file is already configured in `.gitignore` to prevent committing credentials).*
|
||||
|
||||
```hcl
|
||||
ovh_endpoint = "ovh-eu"
|
||||
ovh_application_key = "your_ovh_application_key"
|
||||
ovh_application_secret = "your_ovh_application_secret"
|
||||
ovh_consumer_key = "your_ovh_consumer_key"
|
||||
ovh_service_name = "your_ovh_public_cloud_project_id"
|
||||
ssh_public_key = "your_ssh_public_key_content" # or file("~/.ssh/id_rsa.pub")
|
||||
```
|
||||
|
||||
### Step 3: Initialize with Gitea State Backend
|
||||
To keep your state synchronized with Gitea Actions, run `terraform init` locally and point it to Gitea's built-in Terraform State Registry:
|
||||
|
||||
```bash
|
||||
cd terraform
|
||||
|
||||
terraform init \
|
||||
-backend-config="address=https://<your-gitea-domain>/api/v1/packages/<your-gitea-username>/terraform/state/coolify" \
|
||||
-backend-config="username=<your-gitea-username>" \
|
||||
-backend-config="password=<your-gitea-token-or-password>"
|
||||
```
|
||||
|
||||
### Step 4: Plan and Apply
|
||||
Now you can plan and apply your local changes. They will automatically read from and write to the exact same state file as Gitea Actions:
|
||||
|
||||
```bash
|
||||
# Preview changes
|
||||
terraform plan
|
||||
|
||||
# Apply changes to provision/update the VPS
|
||||
terraform apply
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Customization
|
||||
|
||||
You can customize the deployment by overriding the default values in `terraform/variables.tf`:
|
||||
|
||||
- **OVH Endpoint (`ovh_endpoint`)**: Defaults to `ovh-eu`. Change to `ovh-ca` or `ovh-us` depending on your account region.
|
||||
- **Region (`region`)**: Defaults to `GRA11` (Gravelines, France). Other popular regions: `SBG5` (Strasbourg), `WAW1` (Warsaw), `DE1` (Frankfurt), `UK1` (London).
|
||||
- **Instance Size/Flavor (`flavor_name`)**: Defaults to `b2-7` (2 vCPUs, 7 GB RAM). If you want a smaller/starter setup, you can use `s1-2` (1 vCPU, 2 GB RAM) or `d2-4` (2 vCPUs, 4 GB RAM). Coolify operates best with at least 2 vCPUs and 4 GB RAM.
|
||||
- **Operating System (`image_name`)**: Defaults to `Ubuntu 22.04`. Coolify supports Ubuntu 22.04/24.04 and Debian 11/12 out of the box.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Post-Deployment: Accessing Coolify
|
||||
|
||||
Once the Gitea Actions pipeline completes successfully:
|
||||
|
||||
1. Copy the public IP address of the server from the Terraform output or the Gitea Actions log.
|
||||
2. Open your browser and navigate to `http://<your-vps-ip>:8000`.
|
||||
3. Register your administrator account.
|
||||
4. Set up your wild-card domain or custom domains inside Coolify!
|
||||
|
||||
Enjoy your brand-new, self-hosted, enterprise-grade PaaS on OVH VPS!
|
||||
@@ -0,0 +1,5 @@
|
||||
[defaults]
|
||||
inventory = inventory.ini
|
||||
host_key_checking = False
|
||||
retry_files_enabled = False
|
||||
stdout_callback = yaml
|
||||
@@ -0,0 +1,2 @@
|
||||
[coolify_vps]
|
||||
${vps_ip} ansible_user=ubuntu
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
- name: Configure VPS and Install Coolify
|
||||
hosts: coolify_vps
|
||||
become: true
|
||||
gather_facts: true
|
||||
|
||||
tasks:
|
||||
- name: Wait for VPS to become reachable via SSH
|
||||
ansible.builtin.wait_for_connection:
|
||||
delay: 5
|
||||
timeout: 300
|
||||
|
||||
- name: Update apt package cache
|
||||
ansible.builtin.apt:
|
||||
update_cache: yes
|
||||
cache_valid_time: 3600
|
||||
|
||||
- name: Install system prerequisites
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- curl
|
||||
- git
|
||||
- ufw
|
||||
- jq
|
||||
state: present
|
||||
|
||||
- name: Configure UFW firewall
|
||||
block:
|
||||
- name: Allow SSH traffic
|
||||
ansible.builtin.shell: ufw allow 22/tcp
|
||||
- name: Allow HTTP traffic
|
||||
ansible.builtin.shell: ufw allow 80/tcp
|
||||
- name: Allow HTTPS traffic
|
||||
ansible.builtin.shell: ufw allow 443/tcp
|
||||
- name: Allow Coolify Console traffic
|
||||
ansible.builtin.shell: ufw allow 8000/tcp
|
||||
- name: Allow Realtime/Websocket traffic
|
||||
ansible.builtin.shell: ufw allow 6001/tcp
|
||||
- name: Enable UFW firewall
|
||||
ansible.builtin.shell: echo "y" | ufw enable
|
||||
rescue:
|
||||
- name: Log firewall configuration failure
|
||||
ansible.builtin.debug:
|
||||
msg: "Failed to set up firewall, continuing with installation."
|
||||
|
||||
- name: Check if Coolify is already installed
|
||||
ansible.builtin.stat:
|
||||
path: /data/coolify/source/.env
|
||||
register: coolify_installed
|
||||
|
||||
- name: Install Coolify via official installation script
|
||||
ansible.builtin.shell: |
|
||||
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
||||
register: install_output
|
||||
when: not coolify_installed.stat.exists
|
||||
async: 600
|
||||
poll: 10
|
||||
|
||||
- name: Print installation output
|
||||
ansible.builtin.debug:
|
||||
var: install_output.stdout_lines
|
||||
when: install_output.changed
|
||||
@@ -0,0 +1,54 @@
|
||||
resource "ovh_cloud_project_keypair" "deploy_key" {
|
||||
service_name = var.ovh_service_name
|
||||
name = "coolify_deploy_key"
|
||||
public_key = var.ssh_public_key
|
||||
region = var.region
|
||||
}
|
||||
|
||||
# 1. Look up the hardware flavor ID dynamically using the flavor name
|
||||
data "ovh_cloud_project_flavors" "flavor" {
|
||||
service_name = var.ovh_service_name
|
||||
region = var.region
|
||||
name_filter = var.flavor_name
|
||||
}
|
||||
|
||||
# 2. Look up the image ID list dynamically for the region
|
||||
data "ovh_cloud_project_images" "images" {
|
||||
service_name = var.ovh_service_name
|
||||
region = var.region
|
||||
}
|
||||
|
||||
resource "ovh_cloud_project_instance" "coolify" {
|
||||
service_name = var.ovh_service_name
|
||||
name = var.instance_name
|
||||
region = var.region
|
||||
billing_period = "hourly"
|
||||
|
||||
# Use flavor block with the retrieved ID
|
||||
flavor {
|
||||
flavor_id = tolist(data.ovh_cloud_project_flavors.flavor.flavors)[0].id
|
||||
}
|
||||
|
||||
# Use boot_from block to filter and select the correct image ID
|
||||
boot_from {
|
||||
image_id = [for img in data.ovh_cloud_project_images.images.images : img.id if img.name == var.image_name][0]
|
||||
}
|
||||
|
||||
# Use network block to assign a public IP
|
||||
network {
|
||||
public = true
|
||||
}
|
||||
|
||||
# Use ssh_key block to attach the deploy key
|
||||
ssh_key {
|
||||
name = ovh_cloud_project_keypair.deploy_key.name
|
||||
}
|
||||
}
|
||||
|
||||
# Generate inventory file for Ansible (using the updated addresses structure)
|
||||
resource "local_file" "ansible_inventory" {
|
||||
content = templatefile("${path.module}/../ansible/inventory.tpl", {
|
||||
vps_ip = [for addr in ovh_cloud_project_instance.coolify.addresses : addr.ip if addr.version == 4][0]
|
||||
})
|
||||
filename = "${path.module}/../ansible/inventory.ini"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
output "vps_ip" {
|
||||
description = "The public IPv4 address of the Coolify VPS"
|
||||
value = [for addr in ovh_cloud_project_instance.coolify.addresses : addr.ip if addr.version == 4][0]
|
||||
}
|
||||
|
||||
output "instance_id" {
|
||||
description = "The ID of the provisioned OVH instance"
|
||||
value = ovh_cloud_project_instance.coolify.id
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
terraform {
|
||||
required_version = ">= 1.3.0"
|
||||
required_providers {
|
||||
ovh = {
|
||||
source = "ovh/ovh"
|
||||
version = "~> 0.43.0"
|
||||
}
|
||||
}
|
||||
backend "http" {} # Allows local and Gitea Actions to share state securely
|
||||
}
|
||||
|
||||
provider "ovh" {
|
||||
endpoint = var.ovh_endpoint
|
||||
application_key = var.ovh_application_key
|
||||
application_secret = var.ovh_application_secret
|
||||
consumer_key = var.ovh_consumer_key
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
variable "ovh_endpoint" {
|
||||
type = string
|
||||
description = "OVH API endpoint (e.g., ovh-eu, ovh-us, ovh-ca)"
|
||||
default = "ovh-eu"
|
||||
}
|
||||
|
||||
variable "ovh_application_key" {
|
||||
type = string
|
||||
description = "OVH Application Key"
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "ovh_application_secret" {
|
||||
type = string
|
||||
description = "OVH Application Secret"
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "ovh_consumer_key" {
|
||||
type = string
|
||||
description = "OVH Consumer Key"
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "ovh_service_name" {
|
||||
type = string
|
||||
description = "OVH Public Cloud Project ID (Service Name)"
|
||||
}
|
||||
|
||||
variable "region" {
|
||||
type = string
|
||||
description = "OVH Region to deploy the VPS in (e.g., GRA11, SBG5, WAW1)"
|
||||
default = "GRA11"
|
||||
}
|
||||
|
||||
variable "instance_name" {
|
||||
type = string
|
||||
description = "Name of the VPS/Instance"
|
||||
default = "coolify-vps"
|
||||
}
|
||||
|
||||
variable "flavor_name" {
|
||||
type = string
|
||||
description = "The hardware flavor / size of the VPS (Coolify recommends at least 2 vCPUs and 4GB RAM)"
|
||||
default = "b2-7" # 2 vCPUs, 7GB RAM (excellent for Coolify)
|
||||
}
|
||||
|
||||
variable "image_name" {
|
||||
type = string
|
||||
description = "The OS Image name"
|
||||
default = "Ubuntu 22.04"
|
||||
}
|
||||
|
||||
variable "ssh_public_key" {
|
||||
type = string
|
||||
description = "SSH public key content to be authorized on the VPS"
|
||||
}
|
||||
Reference in New Issue
Block a user