Hetzner VPS

This templates allows setting up and deploy an application to a VPS server hosted on Hetzner.

Features

  • Set up a server on Hetzner from your local CLI with a single command
  • Server hardened (e.g. SSH) by default
  • Customise server configuration using Terraform
  • Deploy an application (e.g. docker-compose.yml) from your development machine or CI pipeline to Hetzner

Configure

The following key properties need to be configured for this template:

  • Location: The Hetzner location this server should be deployed to. This can be any valid location string without spaces. For instance, if the location fsn1 is chosen, the server will be deployed to the fsn1 Hetzner data center. Example pattern: ^[^\s]*
  • Server Name: The Hetzner server name that should be used for this server. The name must not contain any spaces. For example, if the server name my-server is chosen, the server will be identified as my-server. Example pattern: ^[^\s]*
  • Server Type: The Hetzner server type that should be used for this server. The server type string must not contain any spaces. For example, cx11 refers to a small cloud server instance. Example pattern: ^[^\s]*
  • SSH User Fingerprint (optional): The SSH fingerprint of the user that should be granted access to the server. This must be a valid SSH fingerprint string, for example, SHA256:xyz. Example pattern: ^[^\s]*
  • Only Allow SSH Access from IP (optional): The specific IP address from which SSH access is allowed. For instance, setting this to 192.168.1.1 would limit SSH access to that IP address only.
  • Environment Variables (optional): The environment variables that should be set for the server. Each environment variable consists of a name and a value. For example, a variable with the name NODE_ENV and the value production can be configured to set the environment to production.

For more details on deployment, refer to the Hetzner server deployment configuration in the Goldstack documentation.

Getting Started

1. Project Setup

Before using this template, you need to configure the project. For this, please see the Getting Started Guide on the Goldstack documentation.

2. Setup Infrastructure

To stand up the infrastructure for this module, find the directory for this module in the packages/ folder and navigate to this folder in the command line. Then identify the name of the deployment you have defined in the Goldstack configuration tool. This can be found in the packages/[moduleName]/goldstack.json file. Look for the "deployments" property and there for the "name" of the first deployment. The name should either be dev or prod.

In order to stand up the infrastructure, run the following command:

yarn infra up [deploymentName]

This will be either yarn infra up dev or yarn infra up prod depending on your choice of deployment. Note that running this command can take a while.

3. Development

The application deployed to Hetzner is defined in the server/ directory.

You can edit the files here. The start.sh script will be run every time a deployment is made to the server using yarn deploy [deployment].

If using docker-compose, you should also be able to develop locally easily. Simply provide a .env file with the environment configuration for your local development environment.

You may also define secrets in the server/secrets/ folder as individual text files that contain nothing but the secret value (e.g. server/secrets/my_secret.txt).

For your remote environments defined in goldstack.json, environment variables will be defined from the properties in goldstack.json. Secrets will be read from the file credentials.json. Use the following format to define secrets in credentials.json:

{
  "prod": {
    "dummy": "thats the value"
  }
}

This credentials.json will result in the file /home/goldstack/app/secrets/dummy.txt to be created with the content thats the value. This should make it easy to use it in a docker-compose file, see How to use secrets in Docker Compose.

Infrastructure

All infrastructure for this module is defined in Terraform. You can find the Terraform files for this template in the directory [moduleDir]/infra/aws. You can define multiple deployments for this template, for instance for development, staging and production environments.

If you configured AWS deployment before downloading your project, the deployments and their respective configurations are defined in [moduleDir]/goldstack.json.

The configuration tool will define one deployment. This will be either dev or prod depending on your choice during project configuration. In the example goldstack.json below, a deployment with the name dev is defined.

{
  "$schema": "./schemas/package.schema.json",
  "name": "...",
  "template": "...",
  "templateVersion": "...",
  "configuration": {},
  "deployments": [
    {
      "name": "dev",
      "awsRegion": "us-west-2",
      "awsUser": "awsUser",
      "configuration": {
        ...
      }
    }
  ]
}

Infrastructure Commands

Infrastructure commands for this template can be run using yarn. There are four commands in total:

  • yarn infra up: For standing up infrastructure.
  • yarn infra init: For initialising Terraform.
  • yarn infra plan: For running Terraform plan.
  • yarn infra apply: For running Terraform apply.
  • yarn infra destroy: For destroying all infrastructure using Terraform destroy.
  • yarn infra upgrade: For upgrading the Terraform versions (supported by the template). To upgrade to an arbitrary version, use yarn infra terraform.
  • yarn infra terraform: For running arbitrary Terraform commands.

For each command, the deployment they should be applied to must be specified.

yarn infra [command] [deploymentName]

For instance, to stand up the infrastructure for the dev deployment, the following command would need to be issued:

yarn infra up dev

Generally you will only need to run yarn infra up. However, if you are familiar with Terraform and want more fine-grained control over the deployment of your infrastructure, you can also use the other commands as required.

Note that for running yarn infra terraform, you will need to specify which command line arguments you want to provide to Terraform. By default, no extra arguments are provided:

yarn infra terraform [deployment] plan

If extra arguments are needed, such as variables, you can use the --inject-variables option, such as for running terraform plan:

yarn infra terraform [deployment] --inject-variables plan

If you want to interact with the remote backend, you can also provide the --inject-backend-config option, such as for running terraform init:

yarn infra terraform [deployment] --inject-backend-config init

Customizing Terraform

Goldstack templates make it very easy to customize infrastructure to your specific needs. The easiest way to do this is to simply edit the *.tf files in the infra/aws folder. You can make the changes you need and then run yarn infra up [deploymentName] to apply the changes.

The infra/aws folder contains a file variables.tf that contains the variables required for your deployment; for instance the domain name for a website. The values for these variables are defined in the module's goldstack.json file in the "configuration" property. There is one global configuration property that applies for all deployments and each deployment also has its own configuration property. In order to add a new variable, add the variable to variables.tf and then add it to the configuration for your template or to the configurations for the deployments.

Note that due to JavaScript and Terraform using different conventions for naming variables, Goldstack applies a basic transformation to variable names. Camel-case variables names are converted to valid variables names for Terraform by replacing every instance of a capital letter C with _c in the variable name. For instance:

myVariableName in the Goldstack configuration will translate to the Terraform variable my_variable_name as defined in variables.tf.

Terraform State

In order to manage your infrastructure, Terraform maintains a state for each deployment; to calculate required changes when the infrastructure is updated and also for destroying the infrastructure if it is no longer required. Goldstack by default will store the terraform state in the infra/aws folder as simple files.

This works well for deploying infrastructure from your local development environment but is not a good choice when building a CI/CD pipeline for the infrastructure definition. In that case, it is better to define Remote State. A popular choice many projects adopt here is to store the state in an S3 bucket. Please see the Terraform documentation for further details.

Hetzner Credentials

To use the Hetzner Terraform provider, a token is required. To generate a token, please see Hetzner Documentation: Generating an API token. Goldstack will look for this token by looking up the user matching the deployment in: config/infra/hetzner/config.json.

You can provide the secret as follows:

{
  "users": [
    {
      "name": "max",
      "config": {
        "token": "xxx"
      }
    }
  ]
}

Goldstack will also look for the environment variable HCLOUD_TOKEN, and if that is defined, will use that as the token for the Hetzner deployment. This makes it easy to provide this in GitHub actions.

Anything in config.json will be ignored if the environment variable is set.

GitHub Actions

To deploy from GitHub actions, you will need an SSH user.

For this, first create an SSH key and add it to Hetzner, see How to create an SSH key and attach it to a Hetzner server.

Note we will need the fingerprint in configuring goldstack.json.

Secrets

The VPS will by default be provided with credentials for the AWS IAM user that is used to deploy files to the server.

Additional secrets can be defined by manually editing the file: dist/credentials/credentials.

Any additional properties added to the JSON file will be unpacked into files in the ~/app/secrets folder.

goldstack@goldstack-docker:~/app$ ls secrets/
# -> accessKeyId.txt  awsRegion.txt  mySecret.txt  secretAccessKey.txt

These should be easy to consume as secrets in a Docker Compose file.

services:
  myapp:
    image: myapp:latest
    secrets:
      - my_secret
secrets:
  my_secret:
    file: ./my_secret.txt

Editing the file dist/credentials/credentials manually can be useful during development. However, a better way is to define a GitHub action to deploy the server.

In the GitHub action, one can use GitHub secrets to set the content of dist/credentials/credentials with the secrets required before the infrastructure is created and before a deployment.

- name: Create JSON file
  run: |
    echo '{
      "secret1": "${{ secrets.SECRET1 }}",
      "secret2": "${{ secrets.SECRET2 }}",
      "secret3": "${{ secrets.SECRET3 }}"
    }' > dist/credentials/credentials

Note the key / name of the secret should always be in camel case.

© 2024 Pureleap Pty. Ltd. and Contributors