Getting Started

  • Connecting hstacks to Hetzner Cloud

Key Terms & Concepts

  • hstacks

    hstacks is an Infrastructure as Code (IaC) platform for creating, managing, and deleting Stacks in a Hetzner Cloud account.

  • Stack

    A Stack is a collection of one or more Cloud resources (Servers, Volumes, Firewalls, etc) which are described in a JSON file.

  • Tokens

    A hstacks token is associated with a user. It's a means to authenticate and interact with a hstacks account.

  • Keys

    A hstacks key contains a reference to an encrypted Hetzner Cloud API Key.

    Hetzner Cloud API Keys are encrypted with 4096-bit encryption keys, and are never displayed once stored.

  • Summary

    A User has a hstacks Token...

    A hstacks Token has a hstacks Key...

    A hstacks Key contains a Hetzner Cloud API Key...

    Which hstacks uses to deploy Stacks in your Hetzner Cloud account.

Endpoints

Stacks, Tokens, and Keys can be created and destroyed through the hstacks API: https://api.hstacks.dev

  • GET/info/available-images/:architecture?

    Retrieve a list of available images for a specified architecture. If no architecture is specified, all available images are returned.

    • Path Parameter: `architecture` (optional) - The target architecture (e.g., `x86_64`).
    • Authorization: Requires a valid hstacks token with an associated key passed in the 'access-token' header of the request.
    • Response: JSON object containing available images.

      {
          "data": [
              {
                  "name": "ubuntu-20.04",
                  "architecture": "x86"
              },
              {
                  "name": "debian-11",
                  "architecture": "x86"
              },
              {
                  "name": "rocky-8",
                  "architecture": "x86"
              },
              {
                  "name": "centos-stream-9",
                  "architecture": "x86"
              },
              {
                  "name": "ubuntu-22.04",
                  "architecture": "x86"
              },
              {
                  "name": "rocky-9",
                  "architecture": "x86"
              },
              {
                  "name": "centos-stream-9",
                  "architecture": "arm"
              },
              {
                  "name": "debian-11",
                  "architecture": "arm"
              },
              {
                  "name": "rocky-8",
                  "architecture": "arm"
              },
              {
                  "name": "rocky-9",
                  "architecture": "arm"
              },
              {
                  "name": "ubuntu-20.04",
                  "architecture": "arm"
              },
              {
                  "name": "ubuntu-22.04",
                  "architecture": "arm"
              },
              {
                  "name": "alma-8",
                  "architecture": "x86"
              },
              {
                  "name": "alma-9",
                  "architecture": "x86"
              },
              {
                  "name": "alma-8",
                  "architecture": "arm"
              },
              {
                  "name": "alma-9",
                  "architecture": "arm"
              },
              {
                  "name": "debian-12",
                  "architecture": "x86"
              },
              {
                  "name": "debian-12",
                  "architecture": "arm"
              },
              {
                  "name": "fedora-40",
                  "architecture": "x86"
              },
              {
                  "name": "fedora-40",
                  "architecture": "arm"
              },
              {
                  "name": "ubuntu-24.04",
                  "architecture": "x86"
              },
              {
                  "name": "ubuntu-24.04",
                  "architecture": "arm"
              },
              {
                  "name": "fedora-41",
                  "architecture": "x86"
              },
              {
                  "name": "fedora-41",
                  "architecture": "arm"
              }
          ],
          "msg": "Stack creation initiated",
          "status": "ok"
      }
                                  
    • Example cURL Request:

      curl --location --request GET 'https://api.hstacks.dev/info/available-images' --header 'Content-Type: application/json' --header 'Access-Token: <YOUR_HSTACKS_TOKEN>'
                                  
  • GET/info/available-locations/:serverType?

    Fetch a list of valid locations for a given server type. If no server type is provided, all locations are returned.

    • Path Parameter: `serverType` (optional) - The type of server (e.g., `cx11`) you want to retrieve locations for. If omitted, all available locations will be returned.
    • Authorization: Requires a valid hstacks token with an associated key passed in the 'access-token' header of the request.
    • Response: JSON object containing available locations.

      {
          "data": [
              {
                  "name": "fsn1",
                  "description": "Falkenstein DC Park 1",
                  "country": "DE",
                  "city": "Falkenstein",
                  "latitude": 50.47612,
                  "longitude": 12.370071,
                  "networkZone": "eu-central"
              },
              {
                  "name": "nbg1",
                  "description": "Nuremberg DC Park 1",
                  "country": "DE",
                  "city": "Nuremberg",
                  "latitude": 49.452102,
                  "longitude": 11.076665,
                  "networkZone": "eu-central"
              },
              {
                  "name": "hel1",
                  "description": "Helsinki DC Park 1",
                  "country": "FI",
                  "city": "Helsinki",
                  "latitude": 60.169855,
                  "longitude": 24.938379,
                  "networkZone": "eu-central"
              },
              {
                  "name": "ash",
                  "description": "Ashburn, VA",
                  "country": "US",
                  "city": "Ashburn, VA",
                  "latitude": 39.045821,
                  "longitude": -77.487073,
                  "networkZone": "us-east"
              },
              {
                  "name": "hil",
                  "description": "Hillsboro, OR",
                  "country": "US",
                  "city": "Hillsboro, OR",
                  "latitude": 45.54222,
                  "longitude": -122.951924,
                  "networkZone": "us-west"
              },
              {
                  "name": "sin",
                  "description": "Singapore",
                  "country": "SG",
                  "city": "Singapore",
                  "latitude": 1.283333,
                  "longitude": 103.833333,
                  "networkZone": "ap-southeast"
              }
          ],
          "msg": "Available locations retrieved.",
          "status": "ok"
      }
                                  
    • Example cURL Request:

      curl --location --request GET 'https://api.hstacks.dev/info/available-locations' --header 'Content-Type: application/json' --header 'Access-Token: '<YOUR_HSTACKS_TOKEN>'
                                  
  • GET/info/available-servers/:targetLocation?

    Retrieve a list of server types available in a specific location. If no location is provided, all server types are returned.

    • Path Parameter: `targetLocation` (optional) - The desired location.
    • Authorization: Requires a valid hstacks token with an associated key passed in the 'access-token' header of the request.
    • Response: JSON object containing server types.

      {
          "data": [
              {
                  "name": "cpx11",
                  "description": "CPX 11",
                  "architecture": "x86"
              },
              {
                  "name": "cpx21",
                  "description": "CPX 21",
                  "architecture": "x86"
              },
              {
                  "name": "cpx31",
                  "description": "CPX 31",
                  "architecture": "x86"
              },
              {
                  "name": "cpx41",
                  "description": "CPX 41",
                  "architecture": "x86"
              },
              {
                  "name": "cpx51",
                  "description": "CPX 51",
                  "architecture": "x86"
              },
              {
                  "name": "cax11",
                  "description": "CAX11",
                  "architecture": "arm"
              },
              {
                  "name": "cax21",
                  "description": "CAX21",
                  "architecture": "arm"
              },
              {
                  "name": "cax31",
                  "description": "CAX31",
                  "architecture": "arm"
              },
              {
                  "name": "cax41",
                  "description": "CAX41",
                  "architecture": "arm"
              },
              {
                  "name": "ccx13",
                  "description": "CCX13 Dedicated CPU",
                  "architecture": "x86"
              },
              {
                  "name": "ccx23",
                  "description": "CCX23 Dedicated CPU",
                  "architecture": "x86"
              },
              {
                  "name": "ccx33",
                  "description": "CCX33 Dedicated CPU",
                  "architecture": "x86"
              },
              {
                  "name": "ccx43",
                  "description": "CCX43 Dedicated CPU",
                  "architecture": "x86"
              },
              {
                  "name": "ccx53",
                  "description": "CCX53 Dedicated CPU",
                  "architecture": "x86"
              },
              {
                  "name": "ccx63",
                  "description": "CCX63 Dedicated CPU",
                  "architecture": "x86"
              },
              {
                  "name": "cx22",
                  "description": "CX22",
                  "architecture": "x86"
              },
              {
                  "name": "cx32",
                  "description": "CX32",
                  "architecture": "x86"
              },
              {
                  "name": "cx42",
                  "description": "CX42",
                  "architecture": "x86"
              },
              {
                  "name": "cx52",
                  "description": "CX52",
                  "architecture": "x86"
              }
          ],
          "msg": "Available locations retrieved.",
          "status": "ok"
      }
                                  
    • Example cURL Request:

      curl --location --request GET 'https://api.hstacks.dev/info/available-servers' --header 'Content-Type: application/json' --header 'Access-Token: '<YOUR_HSTACKS_TOKEN>'
                                  
  • POST/keys/create

    Create a new hstacks Key.

    • Request Body: JSON object containing `value` (Hetzner Cloud API Key) and `name` (optional key name).

      {
          "name" : "eph-key",
          "value" : "<HETZNER API KEY>"
      }
                              
    • Authorization: Requires a valid hstacks user session.
    • Response: JSON object with the UUID of the created key.
  • POST/tokens/create

    Create a new hstacks Token associated with an existing Key.

    • Request Body: JSON object containing `key` (UUID of the key) and `name` (optional token name).

      {
          "name" : "Hetzer Token",
          "key" : "<HSTACKS KEY ID>"
      }
                                  
    • Authorization: Requires a valid hstacks user session.
    • Response: JSON object with the UUID of the created token.
  • POST/stacks/create

    Create a new Stack from a JSON description.

    • Request Body: JSON object defining the Stack.

      // An empty example stack
      {
          "name": "example-stack",
          "servers": [],
          "firewalls": [],
          "volumes": [],
          "successHook": "https://example.org/success",
          "errorHook": "https://example.org/error"
      }
                                  
    • Authorization: Requires a valid hstacks token with an associated key passed in the 'access-token' header of the request.
    • Response: JSON object with the ID of the created Stack.

      {
          "data": "<NEW STACK ID>",
          "msg": "Stack creation initiated",
          "status": "ok"
      }
                                  
    • Example cURL Request:

      curl --location --request POST 'http://api.hstacks.dev/stacks/create' --header 'Content-Type: application/json' --header 'Access-Token: <YOUR_HSTACKS_TOKEN>' --data-raw '{
          "name": "example-stack",
          "servers": [],
          "firewalls": [],
          "volumes": [],
          "successHook": "https://example.org/success",
          "errorHook": "https://example.org/error"
      }'
                                  
  • GET/stacks/check/:stackID

    Check the status of an existing Stack by its ID.

    • Path Parameter: `stackID` - The unique identifier of the Stack to check.

    • Authorization: Requires a valid hstacks user session.
    • Response: JSON object with the current status of the Stack.

      // Example response for a successful status retrieval
      {
          "status": "ok",
          "msg": "Status successfully retrieved for Stack '<stackID>'",
          "data": "ACTIVE"
      }
      
      // Example response for a missing or unauthorized Stack
      {
          "status": "err",
          "msg": "Could not find Stack with ID: '<stackID>'"
      }
                                  
  • POST/stacks/delete/:stackID

    Delete an existing Stack by its ID.

    • Path Parameter: `stackID` - The ID of the Stack to delete.
    • Authorization: Requires a valid hstacks token with an associated key passed in the 'access-token' header of the request.
    • Response: JSON object confirming Stack deletion initiation.

      {
          "data": "<stackID>",
          "msg": "Stack deletion initiated.",
          "status": "ok"
      }
                                  
    • Example cURL Request:

      curl --location --request POST 'http://api.hstacks.dev/stacks/delete/<STACK_ID>' --header 'Content-Type: application/json' --header 'Access-Token: <YOUR_HSTACKS_TOKEN>'
                                  

Example Stacks

A few example Stacks to get you started. All of these Stack Templates can be POSTed as with a JSON body tohttps://api.hstacks.dev/api/stacks/createwith a hstacks ACCESS_TOKEN header to deploy the resources deployed within.

  • Single Server + Firewall Example

    This stack will deploy a single cax11 server in the Falkenstein region with Ubuntu 24.04. It will also create a Firewall that enables SSH access and add it to the server.

    There is no SSH key added to the server, so Hetzner will email you credentials to SSH into your server.

    • Stack Template:

      {
        "name" : "my-first-stack",
        "servers": [
          {
            "name": "my-first-server",
            "serverType": "cax11",
            "image": "ubuntu-24.04",
            "location": "fsn1",
            "labels": {
              "project": "demoProject"
            },
            "startAfterCreate": true,
            "firewalls": [
              "ssh"
            ],
            "startScript": "#!/bin/bash\nsudo apt update\nsudo apt upgrade -y"
          }
        ],
        "firewalls": [
          {
            "name": "ssh",
            "rules": [
              {
                "direction": "inbound",
                "protocol": "tcp",
                "port": 22,
                "sourceIPs": [
                  "0.0.0.0/0",
                  " ::/0"
                ],
                "description": "Enable SSH access to a server."
              }
            ],
            "labels": {}
          }
        ],
        "volumes": []
      }
                                  
    • Example cURL Request:

      curl --location --request POST 'https://api.hstacks.dev/api/stacks/create' --header 'Content-Type: application/json' --header 'Access-Token: ' --data-raw '{ "name" : "my-first-stack", "servers": [ { "name": "my-first-server", "serverType": "cax11", "image": "ubuntu-24.04", "location": "fsn1", "labels": { "project": "demoProject" }, "startAfterCreate": true, "firewalls": [ "ssh" ], "startScript": "#!/bin/bash\nsudo apt update\nsudo apt upgrade -y" } ], "firewalls": [ { "name": "ssh", "rules": [ { "direction": "inbound", "protocol": "tcp", "port": 22, "sourceIPs": [ "0.0.0.0/0", " ::/0" ], "description": "Enable SSH access to a server." } ], "labels": {} } ], "volumes": [] }'
                                  
  • NGINX HTTP Server

    This stack will deploy a single cx22 server in the Helsinki region with Ubuntu 24.04 that will run a public HTTP server with NGINX. It will also create a Firewall that enables SSH access and add it to the server.

    There is no SSH key added to the server, so Hetzner will email you credentials to SSH into your server.

    • Stack Template:

      {
        "errorHook": "",
        "firewalls": [
          {
            "labels": {},
            "name": "http-inbound",
            "rules": [
              {
                "description": "Allows HTTP connections over port 80.",
                "direction": "inbound",
                "port": 80,
                "protocol": "tcp",
                "sourceIPs": [
                  "0.0.0.0/0",
                  " ::/0"
                ]
              }
            ]
          }
        ],
        "name": "HTTP-Server-Stack",
        "servers": [
          {
            "environmentVariables": {},
            "firewalls": [
              "http-inbound"
            ],
            "image": "ubuntu-24.04",
            "labels": {},
            "location": "hel1",
            "name": "my-http-server",
            "serverType": "cx22",
            "sshKeyName": "",
            "startAfterCreate": true,
            "startScript": "#!/bin/bash\n# Update package index\napt-get update -y\n\n# Install NGINX\napt-get install -y nginx\n\n# Enable and start NGINX on boot\nsystemctl enable nginx\nsystemctl start nginx\n\n# Optional: Create a custom index.html for demonstration\necho \"

      You've just deployed a HTTP server with hstacks! on Ubuntu 24.04!

      \" > /var/www/html/index.html\n" } ], "successHook": "", "volumes": [] }
    • Example cURL Request:

      curl --location --request POST 'https://api.hstacks.dev/api/stacks/create' --header 'Content-Type: application/json' --header 'Access-Token: ' --data-raw '{"errorHook":"","firewalls":[{"labels":{},"name":"http-inbound","rules":[{"description":"Allows HTTP connections over port 80.","direction":"inbound","port":80,"protocol":"tcp","sourceIPs":["0.0.0.0/0"," ::/0"]}]}],"name":"HTTP-Server","servers":[{"environmentVariables":{},"firewalls":["http-inbound"],"image":"ubuntu-24.04","labels":{},"location":"hel1","name":"my-http-server","serverType":"cx22","sshKeyName":"","startAfterCreate":true,"startScript":"#!/bin/bash\n# Update package index\napt-get update -y\n\n# Install NGINX\napt-get install -y nginx\n\n# Enable and start NGINX on boot\nsystemctl enable nginx\nsystemctl start nginx\n\n"}],"successHook":"","volumes":[]}'
                                  
  • WordPress Installation

    This stack will deploy a single cx22 server in the Helsinki region with Ubuntu 24.04 that will install and configure a WordPress instance. It will also create a Firewall that enables HTTP access and add it to the server.

    There is no SSH key added to the server, so Hetzner will email you credentials to SSH into your server.

    • Stack Template:

      {
        "errorHook": "",
        "firewalls": [
          {
            "name": "http-access",
            "rules": [
              {
                "direction": "inbound",
                "protocol": "tcp",
                "port": 80,
                "sourceIPs": [
                  "0.0.0.0/0",
                  " ::/0"
                ],
                "description": "Enable HTTP access"
              }
            ],
            "labels": {}
          }
        ],
        "name": "wordpress-stack",
        "servers": [
          {
            "environmentVariables": {},
            "firewalls": [
              "http-access"
            ],
            "image": "ubuntu-24.04",
            "labels": {},
            "location": "hel1",
            "name": "wp-runner",
            "serverType": "cax11",
            "sshKeyName": "seans-ssh-key",
            "startAfterCreate": true,
            "startScript": "#!/bin/bash\n\nset -e\n\n# Redirect output to a log file for debugging\nexec > /var/log/userdata.log 2>&1\n\n# Update and upgrade the system\necho \"Updating system...\"\napt update && apt upgrade -y\n\n# Install necessary packages\necho \"Installing required packages...\"\nDEBIAN_FRONTEND=noninteractive apt install -y apache2 mysql-server php libapache2-mod-php php-mysql curl unzip\n\n# Configure MySQL securely without interaction\necho \"Configuring MySQL securely...\"\ndebconf-set-selections <<< \"mysql-server mysql-server/root_password password root_password\"\ndebconf-set-selections <<< \"mysql-server mysql-server/root_password_again password root_password\"\n\n# Ensure MySQL service is running\nsystemctl enable mysql\nsystemctl start mysql\n\n# Create a MySQL database and user for WordPress\nDB_NAME=\"wordpress\"\nDB_USER=\"wordpress_user\"\nDB_PASS=\"secure_password\"\n\necho \"Creating MySQL database and user...\"\nmysql -uroot -proot_password < $VHOST_CONF\n\n    ServerAdmin [email protected]\n    DocumentRoot /var/www/html/wordpress\n    ServerName example.com\n    ServerAlias www.example.com\n\n    \n        AllowOverride All\n    \n\n    ErrorLog \\${APACHE_LOG_DIR}/error.log\n    CustomLog \\${APACHE_LOG_DIR}/access.log combined\n\nEOL\n\na2ensite wordpress.conf\na2dissite 000-default.conf\nsystemctl reload apache2\n\n# Final message\necho \"WordPress installation complete! Please visit your server's IP address or domain name to finish the setup.\"\n"
          }
        ],
        "successHook": "",
        "volumes": []
      }
                                  
    • Example cURL Request:

      curl --location --request POST 'https://api.hstacks.dev/api/stacks/create' --header 'Content-Type: application/json' --header 'Access-Token: <YOUR_HSTACKS_TOKEN>' --data-raw '{ "errorHook": "", "firewalls": [ { "name": "http-access", "rules": [ { "direction": "inbound", "protocol": "tcp", "port": 80, "sourceIPs": [ "0.0.0.0/0", " ::/0" ], "description": "Enable HTTP access" } ], "labels": {} } ], "name": "wordpress-stack", "servers": [ { "environmentVariables": {}, "firewalls": [ "http-access" ], "image": "ubuntu-24.04", "labels": {}, "location": "hel1", "name": "wp-runner", "serverType": "cax11", "sshKeyName": "seans-ssh-key", "startAfterCreate": true, "startScript": "#!/bin/bash\n\nset -e\n\n# Redirect output to a log file for debugging\nexec > /var/log/userdata.log 2>&1\n\n# Update and upgrade the system\necho \"Updating system...\"\napt update && apt upgrade -y\n\n# Install necessary packages\necho \"Installing required packages...\"\nDEBIAN_FRONTEND=noninteractive apt install -y apache2 mysql-server php libapache2-mod-php php-mysql curl unzip\n\n# Configure MySQL securely without interaction\necho \"Configuring MySQL securely...\"\ndebconf-set-selections <<< \"mysql-server mysql-server/root_password password root_password\"\ndebconf-set-selections <<< \"mysql-server mysql-server/root_password_again password root_password\"\n\n# Ensure MySQL service is running\nsystemctl enable mysql\nsystemctl start mysql\n\n# Create a MySQL database and user for WordPress\nDB_NAME=\"wordpress\"\nDB_USER=\"wordpress_user\"\nDB_PASS=\"secure_password\"\n\necho \"Creating MySQL database and user...\"\nmysql -uroot -proot_password <<MYSQL_SCRIPT\nCREATE DATABASE $DB_NAME;\nCREATE USER '\''$DB_USER'\''@'\''localhost'\'' IDENTIFIED BY '\''$DB_PASS'\'';\nGRANT ALL PRIVILEGES ON $DB_NAME.* TO '\''$DB_USER'\''@'\''localhost'\'';\nFLUSH PRIVILEGES;\nMYSQL_SCRIPT\n\necho \"Database $DB_NAME and user $DB_USER created with password $DB_PASS.\"\n\n# Download and configure WordPress\necho \"Downloading WordPress...\"\ncd /tmp\ncurl -O https://wordpress.org/latest.zip\nunzip latest.zip\n\n# Move WordPress files to the web directory\necho \"Configuring WordPress files...\"\nmv wordpress /var/www/html/wordpress\nchown -R www-data:www-data /var/www/html/wordpress\nchmod -R 755 /var/www/html/wordpress\n\n# Create WordPress config file\ncd /var/www/html/wordpress\ncp wp-config-sample.php wp-config.php\nsed -i \"s/database_name_here/$DB_NAME/\" wp-config.php\nsed -i \"s/username_here/$DB_USER/\" wp-config.php\nsed -i \"s/password_here/$DB_PASS/\" wp-config.php\n\n# Enable Apache2 rewrite module and restart the server\necho \"Enabling Apache2 rewrite module...\"\na2enmod rewrite\nsystemctl restart apache2\n\n# Create Apache virtual host for WordPress\nVHOST_CONF=\"/etc/apache2/sites-available/wordpress.conf\"\necho \"Creating Apache virtual host for WordPress...\"\ncat <<EOL > $VHOST_CONF\n<VirtualHost *:80>\n ServerAdmin [email protected]\n DocumentRoot /var/www/html/wordpress\n ServerName example.com\n ServerAlias www.example.com\n\n <Directory /var/www/html/wordpress/>\n AllowOverride All\n </Directory>\n\n ErrorLog \\${APACHE_LOG_DIR}/error.log\n CustomLog \\${APACHE_LOG_DIR}/access.log combined\n</VirtualHost>\nEOL\n\na2ensite wordpress.conf\na2dissite 000-default.conf\nsystemctl reload apache2\n\n# Final message\necho \"WordPress installation complete! Please visit your server'\''s IP address or domain name to finish the setup.\"\n" } ], "successHook": "", "volumes": []}'