Author SHA1 Message Date
alessandrovitali fa568ada08 feat: add generated README 2026-08-18 15:25:25 +02:00
+133
View File
@@ -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`.