From fa568ada08ee342e20b72cc2bc2f3e15e82a7a2c Mon Sep 17 00:00:00 2001 From: alessandrovitali Date: Tue, 18 Aug 2026 15:25:25 +0200 Subject: [PATCH] feat: add generated README --- README.md | 133 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..90af7fa --- /dev/null +++ b/README.md @@ -0,0 +1,133 @@ +# dokploy + +Terraform + Ansible setup for a self-hosted [Dokploy](https://dokploy.com/) instance. + +- **Terraform** provisions a Hetzner Cloud VPS, registers an SSH key, creates a Cloudflare DNS record, and writes the Ansible inventory. +- **Ansible** configures the host afterwards (base / users / security roles). + +## Repository layout + +``` +. +├── terraform/ +│ ├── main.tf +│ ├── providers.tf +│ ├── variables.tf +│ ├── terraform.tfvars # non-secret config (committed) +│ ├── .env.example # template for required secrets (committed) +│ ├── .env # real secrets (gitignored — created locally) +│ └── .envrc # direnv loader (committed) +└── ansible/ + ├── playbook.yml + ├── requirements.yml + └── group_vars/ +``` + +## Secrets + +Secrets are **never** committed and never written into `.tf` or `.tfvars` files. +They live only in `terraform/.env` and are injected as `TF_VAR_*` environment +variables, which Terraform maps to input variables automatically. + +| Variable | Environment variable | Committed to git | +| --- | --- | --- | +| `hcloud_token` | `TF_VAR_hcloud_token` | No | +| `cloudflare_api_token` | `TF_VAR_cloudflare_api_token` | No | +| `domain_name` | `TF_VAR_domain_name` | No | + +Everything else (server type, image, location, project name, SSH public key, +DNS record name, proxied flag) is non-secret and lives in `terraform/terraform.tfvars`. + +### `.env` format + +`terraform/.env`: + +```env +TF_VAR_hcloud_token=... +TF_VAR_cloudflare_api_token=... +TF_VAR_domain_name=... +``` + +## Local setup + +### Prerequisites + +- Terraform `>= 1.0` +- [direnv](https://direnv.net/) (recommended) +- A Hetzner Cloud API token +- A Cloudflare API token with `Zone.DNS` edit permission + +### Steps + +```sh +# 1. Create your local secrets file from the template, then fill in the values. +cp terraform/.env.example terraform/.env + +# 2. Allow direnv to load it (evaluates terraform/.envrc). +cd terraform +direnv allow + +# 3. Provision. +terraform init +terraform plan +terraform apply +``` + +`terraform/.envrc` loads the `.env` file: + +```sh +dotenv_if_exists .env +``` + +direnv loads the variables when you `cd` into `terraform/` and unloads them when +you leave, so secrets never linger in your shell. + +### Without direnv + +Load the file manually, scoped to a single command: + +```sh +cd terraform +set -a; . ./.env; set +a +terraform plan +``` + +Or use a wrapper that keeps the variables contained to the `terraform` subprocess: + +```sh +#!/bin/sh +set -a +. ./.env +set +a +exec terraform "$@" +``` + +## CI/CD + +CI reads the same variables from its secret store — no `.env` file is needed. + +GitHub Actions example: + +```yaml +env: + TF_VAR_hcloud_token: ${{ secrets.TF_VAR_HCLOUD_TOKEN }} + TF_VAR_cloudflare_api_token: ${{ secrets.TF_VAR_CLOUDFLARE_API_TOKEN }} + TF_VAR_domain_name: ${{ secrets.TF_VAR_DOMAIN_NAME }} + +steps: + - uses: actions/checkout@v4 + - uses: hashicorp/setup-terraform@v3 + - run: terraform init + - run: terraform plan + - run: terraform apply -auto-approve +``` + +## Security notes + +- Never commit `terraform/.env` — it is listed in `.gitignore`. +- `.env.example` is a template only; it must never contain real values. +- If a token is ever exposed (commit, paste, logs, screenshots), rotate it + immediately in the Hetzner / Cloudflare consoles and update `.env`. +- Terraform state files (`*.tfstate`) are gitignored; they can contain derived + values such as the resulting DNS hostname. +- For reproducible CI runs, consider committing `terraform/.terraform.lock.hcl`.