From b9e736cd80617939d306e367fb1297b26fc75fff Mon Sep 17 00:00:00 2001 From: alessandrovitali Date: Wed, 12 Aug 2026 15:01:40 +0200 Subject: [PATCH] feat: fully vibecoded configuration --- .gitea/workflows/deploy.yml | 61 ++++++++++++++ README.md | 163 ++++++++++++++++++++++++++++++++++++ ansible/ansible.cfg | 5 ++ ansible/inventory.tpl | 2 + ansible/playbook.yml | 62 ++++++++++++++ terraform/main.tf | 23 +++++ terraform/outputs.tf | 9 ++ terraform/providers.tf | 17 ++++ terraform/variables.tf | 57 +++++++++++++ 9 files changed, 399 insertions(+) create mode 100644 .gitea/workflows/deploy.yml create mode 100644 README.md create mode 100644 ansible/ansible.cfg create mode 100644 ansible/inventory.tpl create mode 100644 ansible/playbook.yml create mode 100644 terraform/main.tf create mode 100644 terraform/outputs.tf create mode 100644 terraform/providers.tf create mode 100644 terraform/variables.tf diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..4a30850 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..38ea8bb --- /dev/null +++ b/README.md @@ -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:///api/v1/packages//terraform/state/coolify" \ + -backend-config="username=" \ + -backend-config="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://: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! diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg new file mode 100644 index 0000000..4274f92 --- /dev/null +++ b/ansible/ansible.cfg @@ -0,0 +1,5 @@ +[defaults] +inventory = inventory.ini +host_key_checking = False +retry_files_enabled = False +stdout_callback = yaml diff --git a/ansible/inventory.tpl b/ansible/inventory.tpl new file mode 100644 index 0000000..be23ae2 --- /dev/null +++ b/ansible/inventory.tpl @@ -0,0 +1,2 @@ +[coolify_vps] +${vps_ip} ansible_user=ubuntu diff --git a/ansible/playbook.yml b/ansible/playbook.yml new file mode 100644 index 0000000..d06ff6d --- /dev/null +++ b/ansible/playbook.yml @@ -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 diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..c1ecbd2 --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,23 @@ +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 +} + +resource "ovh_cloud_project_instance" "coolify" { + service_name = var.ovh_service_name + name = var.instance_name + region = var.region + flavor_name = var.flavor_name + image_name = var.image_name + key_pair = ovh_cloud_project_keypair.deploy_key.name +} + +# Generate inventory file for Ansible +resource "local_file" "ansible_inventory" { + content = templatefile("${path.module}/../ansible/inventory.tpl", { + vps_ip = ovh_cloud_project_instance.coolify.ipv4_addresses[0] + }) + filename = "${path.module}/../ansible/inventory.ini" +} diff --git a/terraform/outputs.tf b/terraform/outputs.tf new file mode 100644 index 0000000..714bf18 --- /dev/null +++ b/terraform/outputs.tf @@ -0,0 +1,9 @@ +output "vps_ip" { + description = "The public IPv4 address of the Coolify VPS" + value = ovh_cloud_project_instance.coolify.ipv4_addresses[0] +} + +output "instance_id" { + description = "The ID of the provisioned OVH instance" + value = ovh_cloud_project_instance.coolify.id +} diff --git a/terraform/providers.tf b/terraform/providers.tf new file mode 100644 index 0000000..4349694 --- /dev/null +++ b/terraform/providers.tf @@ -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 +} diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..d7c0248 --- /dev/null +++ b/terraform/variables.tf @@ -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" +}