2 Commits
Author SHA1 Message Date
alessandrovitali 14241d4e18 fix(tf): add required_version 2026-08-13 13:44:50 +02:00
alessandrovitali dd0351ad49 feat: terraform configuration for classic VPS 2026-08-13 13:23:05 +02:00
9 changed files with 41 additions and 385 deletions
-61
View File
@@ -1,61 +0,0 @@
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
-163
View File
@@ -1,163 +0,0 @@
# 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!
-5
View File
@@ -1,5 +0,0 @@
[defaults]
inventory = inventory.ini
host_key_checking = False
retry_files_enabled = False
stdout_callback = yaml
-2
View File
@@ -1,2 +0,0 @@
[coolify_vps]
${vps_ip} ansible_user=ubuntu
-62
View File
@@ -1,62 +0,0 @@
---
- 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
+31 -45
View File
@@ -1,54 +1,40 @@
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
}
# Fetch billing subsidiary details from your OVH account profile
data "ovh_me" "myaccount" {}
# 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
}
# Provision the Classic VPS
resource "ovh_vps" "vps_1" {
display_name = "coolify"
# 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
}
ovh_subsidiary = data.ovh_me.myaccount.ovh_subsidiary # Country of billing entity (e.g. "CH", "DE")
plan = [
{
duration = "P1M" # Monthly subscription ("P1M")
plan_code = "vps-le-2-2-40" # Starter plan model (Adjust depending on current catalog)
pricing_mode = "default"
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
# Datacenter configuration
configuration = [
{
label = "vps_datacenter"
value = "LIM" # Limburg (DE)
},
{
label = "vps_os"
value = "ubuntu-22.04"
}
# 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
}
public_ssh_key = var.ssh_public_key
}
# 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"
# The public IP of a classic VPS must be retrieved via a data source after delivery
data "ovh_vps" "vps_1" {
service_name = ovh_vps.vps_1.service_name
}
output "vps_public_ip" {
value = tolist(data.ovh_vps.vps_1.ips)[0]
description = "The public IP address of VPS-1"
}
-9
View File
@@ -1,9 +0,0 @@
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
}
+2 -3
View File
@@ -1,12 +1,11 @@
terraform {
required_version = ">= 1.3.0"
required_version = ">= 1.0.0"
required_providers {
ovh = {
source = "ovh/ovh"
version = "~> 0.43.0"
version = ">= 2.0.0"
}
}
backend "http" {} # Allows local and Gitea Actions to share state securely
}
provider "ovh" {
+4 -31
View File
@@ -1,57 +1,30 @@
variable "ovh_endpoint" {
type = string
description = "OVH API endpoint (e.g., ovh-eu, ovh-us, ovh-ca)"
description = "The OVH API endpoint"
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" {
variable "ovh_project_id" {
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"
description = "The OVH Public Cloud Project ID (Service Name)"
}
variable "ssh_public_key" {
type = string
description = "SSH public key content to be authorized on the VPS"
description = "The public SSH key to install on the VM"
}