Goal
Make Hetzner infrastructure manageable agentically instead of manually through the Hetzner UI.
The immediate use case is spinning up a temporary Hetzner Cloud server to test Elasticsearch. Elasticsearch has relatively heavy memory requirements, so this is a good example of infrastructure we may want to create for an experiment, use for a few hours or days, and then destroy.
Doing this manually through the Hetzner Console is tedious and error-prone. We should be able to tell an agent:
Create an appropriately sized Hetzner instance for Elasticsearch testing, configure access, return the IP/connection details, and later destroy it.
Start with a safe, inspectable implementation and avoid building a large abstraction layer prematurely.
Initial approach
Investigate using an existing Hetzner MCP server first.
Candidate:
hetzner-mcp
- backed by the official Hetzner APIs/OpenAPI specifications
- potentially covers Hetzner Cloud plus other Hetzner services
- usable by Codex, Claude, and other MCP clients
If the MCP implementation is mature enough, prefer it over writing our own provisioning client.
Symfony/PHP integration can come later if an application itself needs runtime access to Hetzner.
Hetzner credentials
Create a dedicated Hetzner Cloud API token for agentic infrastructure work.
Hetzner tokens are project-scoped.
For the first pass:
- use a dedicated Hetzner project if practical
- create a token specifically for agentic/dev infrastructure
- initially test read-only access
- enable write access only when provisioning is ready to test
- keep the token outside the repository
- expose it through environment configuration
Example:
Do not put the token in .env files that can be committed.
Phase 1: inventory
Before provisioning anything, verify that the MCP/API integration can inspect the project.
We should be able to retrieve at least:
- servers
- server types
- locations
- images
- SSH keys
- firewalls
- volumes
- networks
- public IPs
Test queries such as:
List all servers in this Hetzner project.
Show available server types with RAM, CPUs, disk and architecture.
Show available Ubuntu images.
List existing SSH keys.
List existing firewalls.
Show servers currently running in Ashburn.
This gives us confidence that credentials and project scope are correct before allowing mutations.
Phase 2: Elasticsearch test server
Add a repeatable workflow for creating an Elasticsearch test machine.
Initial requirements:
- Ubuntu 24.04 LTS
- x86_64 unless ARM compatibility has been explicitly verified
- enough RAM to make Elasticsearch useful
- public IPv4
- existing SSH key installed
- sensible hostname
- firewall permitting SSH only from appropriate sources
- optionally allow HTTP/HTTPS or Elasticsearch ports later if necessary
Do not expose Elasticsearch port 9200 publicly by default.
A reasonable initial machine should probably have at least:
RAM: 8 GB
CPU: 4 vCPU or better
Disk: 80+ GB
The exact Hetzner server type should be selected from current availability rather than hard-coded permanently.
Example desired interaction:
Create an Elasticsearch test machine in Hetzner.
Requirements:
- Ubuntu 24.04
- at least 8 GB RAM
- x86_64
- use our existing SSH key
- use Ashburn if an appropriate server type is available
- call it elastic-test
The agent should return:
Server: elastic-test
Type: ...
Location: ...
RAM: ...
IPv4: ...
SSH: ssh root@...
Phase 3: bootstrap Elasticsearch
Once provisioning works, automate software installation separately from Hetzner resource creation.
Do not make the Hetzner provisioning layer responsible for Elasticsearch internals.
Possible bootstrap mechanisms:
- cloud-init
- Ansible
- shell bootstrap script
- Docker/Compose
Cloud-init is attractive for the first experiment because Hetzner can apply it when the machine is created.
Example responsibilities:
apt update
install Docker or Elasticsearch packages
configure swap/memory settings if required
start Elasticsearch
verify cluster health
Keep the bootstrap configuration in the repository so it can be reviewed and reproduced.
Prefer Docker for the first Elasticsearch experiment
For an experimental machine, Elasticsearch via Docker is probably the quickest path.
Something approximately like:
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:9.x
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- ES_JAVA_OPTS=-Xms4g -Xmx4g
volumes:
- esdata:/usr/share/elasticsearch/data
ports:
- "127.0.0.1:9200:9200"
volumes:
esdata:
The exact Elasticsearch release should be chosen at implementation time.
Binding to localhost is deliberate. Access it through SSH tunneling unless we later have a reason to expose it.
Example:
ssh -L 9200:localhost:9200 root@SERVER_IP
Then locally:
curl http://localhost:9200
Lifecycle commands/workflow
The important part is not merely creating a server. The lifecycle should be easy enough that temporary infrastructure actually stays temporary.
Desired operations:
create elastic-test
inspect elastic-test
stop elastic-test
start elastic-test
snapshot elastic-test
destroy elastic-test
Destruction should be simple and explicit.
Before destroying a server, report:
- server name
- IP
- attached volumes
- snapshots/backups if relevant
Do not accidentally destroy persistent volumes.
Cost awareness
When creating infrastructure, the agent should report the selected Hetzner server type and its current hourly/monthly price if that information is available from the API.
Example:
Creating:
elastic-test
8 GB RAM
4 vCPU
80 GB disk
Estimated Hetzner cost:
€X/hour
€Y/month maximum
This matters because the intended usage is often short-lived experimentation.
MCP safety model
MCP gives an agent potentially destructive infrastructure capabilities.
We should distinguish operations conceptually:
Read-only
Safe for normal agent use:
list
get
inspect
describe
inventory
Mutating but reversible
Examples:
create server
power on/off
attach volume
change firewall
Destructive
Examples:
delete server
delete volume
rebuild server
delete snapshot
For destructive actions, the agent should clearly identify the target resource before executing them.
If the MCP server supports restricting available tools, consider exposing read/create operations broadly while treating destructive operations more carefully.
Multiple Hetzner projects
Hetzner Cloud API tokens are scoped to a project.
Do not assume one global Hetzner account API gives us everything.
Eventually we may want configuration like:
hetzner:
projects:
dev:
token: '%env(HETZNER_DEV_TOKEN)%'
fortepan:
token: '%env(HETZNER_FORTEPAN_TOKEN)%'
survos:
token: '%env(HETZNER_SURVOS_TOKEN)%'
But this is not required for the initial Elasticsearch experiment.
Start with one development project.
PHP/Symfony fallback
If MCP turns out to be insufficient, use the Hetzner Cloud REST API directly.
Candidate PHP SDK:
composer require lkdevelopment/hetzner-cloud-php-sdk
Alternatively, Symfony HttpClient can call:
https://api.hetzner.cloud/v1/
directly.
Avoid creating a large Symfony bundle until we have a concrete application-level requirement.
If we eventually do add Symfony integration, keep it thin:
HetznerClient
HetznerProjectRegistry
hetzner:inventory
hetzner:server:list
hetzner:server:create
hetzner:server:delete
No Doctrine mirror of Hetzner resources.
Hetzner remains the source of truth.
First implementation target
The first end-to-end success criterion is intentionally small:
Agent receives:
"Create a temporary Hetzner machine for Elasticsearch testing."
Agent:
1. checks available Hetzner server types
2. selects one with >= 8 GB RAM
3. creates Ubuntu 24.04 server
4. installs our SSH key
5. configures a restrictive firewall
6. returns its IP
7. verifies SSH availability
8. bootstraps Elasticsearch
9. verifies Elasticsearch responds
Then:
"Destroy the elastic-test machine."
should remove the temporary infrastructure cleanly.
Acceptance criteria
Non-goals
For this first issue, do not build:
- a generic cloud abstraction
- Terraform infrastructure
- a full Symfony Hetzner bundle
- a local database mirroring Hetzner resources
- production Elasticsearch infrastructure
- public Elasticsearch endpoints
- multi-cloud support
The objective is much simpler:
make disposable Hetzner infrastructure trivial to create and destroy from an agent.
Elasticsearch is the first concrete test case because its memory requirements make local development inconvenient and repeatedly provisioning it through the Hetzner UI is unnecessary friction.
Goal
Make Hetzner infrastructure manageable agentically instead of manually through the Hetzner UI.
The immediate use case is spinning up a temporary Hetzner Cloud server to test Elasticsearch. Elasticsearch has relatively heavy memory requirements, so this is a good example of infrastructure we may want to create for an experiment, use for a few hours or days, and then destroy.
Doing this manually through the Hetzner Console is tedious and error-prone. We should be able to tell an agent:
Start with a safe, inspectable implementation and avoid building a large abstraction layer prematurely.
Initial approach
Investigate using an existing Hetzner MCP server first.
Candidate:
hetzner-mcpIf the MCP implementation is mature enough, prefer it over writing our own provisioning client.
Symfony/PHP integration can come later if an application itself needs runtime access to Hetzner.
Hetzner credentials
Create a dedicated Hetzner Cloud API token for agentic infrastructure work.
Hetzner tokens are project-scoped.
For the first pass:
Example:
export HETZNER_TOKEN=...Do not put the token in
.envfiles that can be committed.Phase 1: inventory
Before provisioning anything, verify that the MCP/API integration can inspect the project.
We should be able to retrieve at least:
Test queries such as:
This gives us confidence that credentials and project scope are correct before allowing mutations.
Phase 2: Elasticsearch test server
Add a repeatable workflow for creating an Elasticsearch test machine.
Initial requirements:
Do not expose Elasticsearch port
9200publicly by default.A reasonable initial machine should probably have at least:
The exact Hetzner server type should be selected from current availability rather than hard-coded permanently.
Example desired interaction:
The agent should return:
Phase 3: bootstrap Elasticsearch
Once provisioning works, automate software installation separately from Hetzner resource creation.
Do not make the Hetzner provisioning layer responsible for Elasticsearch internals.
Possible bootstrap mechanisms:
Cloud-init is attractive for the first experiment because Hetzner can apply it when the machine is created.
Example responsibilities:
Keep the bootstrap configuration in the repository so it can be reviewed and reproduced.
Prefer Docker for the first Elasticsearch experiment
For an experimental machine, Elasticsearch via Docker is probably the quickest path.
Something approximately like:
The exact Elasticsearch release should be chosen at implementation time.
Binding to localhost is deliberate. Access it through SSH tunneling unless we later have a reason to expose it.
Example:
Then locally:
Lifecycle commands/workflow
The important part is not merely creating a server. The lifecycle should be easy enough that temporary infrastructure actually stays temporary.
Desired operations:
Destruction should be simple and explicit.
Before destroying a server, report:
Do not accidentally destroy persistent volumes.
Cost awareness
When creating infrastructure, the agent should report the selected Hetzner server type and its current hourly/monthly price if that information is available from the API.
Example:
This matters because the intended usage is often short-lived experimentation.
MCP safety model
MCP gives an agent potentially destructive infrastructure capabilities.
We should distinguish operations conceptually:
Read-only
Safe for normal agent use:
Mutating but reversible
Examples:
Destructive
Examples:
For destructive actions, the agent should clearly identify the target resource before executing them.
If the MCP server supports restricting available tools, consider exposing read/create operations broadly while treating destructive operations more carefully.
Multiple Hetzner projects
Hetzner Cloud API tokens are scoped to a project.
Do not assume one global Hetzner account API gives us everything.
Eventually we may want configuration like:
But this is not required for the initial Elasticsearch experiment.
Start with one development project.
PHP/Symfony fallback
If MCP turns out to be insufficient, use the Hetzner Cloud REST API directly.
Candidate PHP SDK:
Alternatively, Symfony HttpClient can call:
directly.
Avoid creating a large Symfony bundle until we have a concrete application-level requirement.
If we eventually do add Symfony integration, keep it thin:
No Doctrine mirror of Hetzner resources.
Hetzner remains the source of truth.
First implementation target
The first end-to-end success criterion is intentionally small:
Then:
should remove the temporary infrastructure cleanly.
Acceptance criteria
Non-goals
For this first issue, do not build:
The objective is much simpler:
make disposable Hetzner infrastructure trivial to create and destroy from an agent.
Elasticsearch is the first concrete test case because its memory requirements make local development inconvenient and repeatedly provisioning it through the Hetzner UI is unnecessary friction.