Get Rewarded! We will reward you with up to €50 credit on your account for every tutorial that you write and we publish!

Getting Started with Pulumi and TypeScript on Hetzner Cloud

profile picture
Author
Salem Aljebaly
Published
2026-03-19
Time to read
9 minutes reading time

Introduction

If you have worked with Terraform or OpenTofu before, you know the routine — you write HCL configuration files, and the tool turns them into real infrastructure. It works well, but it has a ceiling. You cannot use a real programming language. No loops that behave like actual loops, no functions that return values, no types that catch mistakes before you deploy. Everything is a workaround.

Pulumi takes a different approach. You write real code — TypeScript, Python, Go, or other languages — and that code provisions your infrastructure. The same language you use to build your application is the same language you use to manage the servers it runs on. For developers, this changes everything.

This tutorial walks through setting up Pulumi with TypeScript to provision your first Hetzner Cloud server. By the end, you will have a working server running on Hetzner, provisioned entirely through code you can read, version, and extend like any other TypeScript project.

Why Pulumi over Terraform / OpenTofu

This question comes up a lot, so it is worth addressing directly. Terraform and OpenTofu are excellent tools, and there is nothing wrong with using them. But there are real differences:

Terraform / OpenTofu Pulumi
Language HCL (custom syntax) TypeScript, Python, Go, C#
Loops for_each, count (limited) Real for loops
Conditionals count = condition ? 1 : 0 Real if statements
Functions Limited built-ins Any function you can write
Type safety No Yes (with TypeScript)
Testing Limited Full unit testing support
IDE support Basic Full autocomplete, errors

If your infrastructure is mostly static — a few servers, a load balancer, some DNS — Terraform is perfectly fine. If you are building complex infrastructure that changes, scales, or has conditional logic, Pulumi is worth the switch. The real programming language is not a gimmick; it genuinely makes complex infrastructure easier to manage.

Prerequisites

Example values used in this tutorial

Item Value
Server name my-first-server
Location nbg1 (Nuremberg)
Server type cx23
OS image ubuntu-24.04

Step 1 - Install the Pulumi CLI

If you have not installed Pulumi yet, the quickest way on macOS or Linux is:

curl -fsSL https://get.pulumi.com | sh

On Windows, use the installer from pulumi.com/docs/install.

After installation, verify it works:

pulumi version
# v3.x.x

Pulumi stores stack state — the record of what infrastructure exists — either in Pulumi Cloud (free for individuals) or locally. This tutorial uses local state so you do not need to create an account:

pulumi login --local

Step 2 - Create a new Pulumi project

Create a directory for your project and initialize it:

mkdir hetzner-pulumi-intro && cd hetzner-pulumi-intro
pulumi new typescript -y

The -y flag accepts all defaults. Pulumi creates the following structure:

hetzner-pulumi-intro/
├── index.ts          ← your infrastructure code goes here
├── package.json
├── tsconfig.json
└── Pulumi.yaml       ← project name and runtime

Install the Hetzner Cloud provider:

npm install @pulumi/hcloud

Step 3 - Configure your API token

Pulumi manages configuration and secrets per stack. Store your Hetzner API token as an encrypted secret:

export HCLOUD_TOKEN=your-api-token-here
pulumi config set --secret hcloudToken $HCLOUD_TOKEN

If Pulumi prompts you for a secrets passphrase when using the local backend, choose one and keep it available for later Pulumi commands.

The --secret flag encrypts the value in the stack configuration file. It will never appear in plain text in your code or version control.

Step 4 - Write your first infrastructure code

Open index.ts and replace its contents with the following:

import * as pulumi from "@pulumi/pulumi";
import * as hcloud from "@pulumi/hcloud";

// Read the API token from stack config (encrypted)
const config = new pulumi.Config();
const hcloudToken = config.requireSecret("hcloudToken");

// Create the Hetzner Cloud provider
const provider = new hcloud.Provider("hcloud", {
  token: hcloudToken,
});

// Provision a server
const server = new hcloud.Server("my-first-server", {
  name:       "my-first-server",
  serverType: "cx23",
  image:      "ubuntu-24.04",
  location:   "nbg1",
  labels: {
    managedBy: "pulumi",
    env:       "tutorial",
  },
}, { provider });

// Export the server's public IP so you can connect to it
export const serverIp   = server.ipv4Address;
export const serverName = server.name;

This is worth reading line by line because it is simpler than it looks:

  • pulumi.Config() reads values from your stack configuration — the token you set in Step 3
  • hcloud.Provider tells Pulumi to use your Hetzner API token for all resources
  • hcloud.Server creates a server — the arguments map directly to Hetzner's API
  • export const makes the server IP available as a stack output after deployment

The key thing to notice: this is real TypeScript. You can put this in a function, loop over it to create multiple servers, import it from another file, or write a unit test for it. Everything you know about programming applies here.

Step 5 - Preview and deploy

Before creating anything, preview what Pulumi will do:

pulumi preview

The output shows exactly what will be created:

Previewing update (dev):
     Type                        Name                      Plan
 +   pulumi:pulumi:Stack         hetzner-pulumi-intro-dev  create
 +   ├─ pulumi:providers:hcloud  hcloud                    create
 +   └─ hcloud:index:Server      my-first-server           create

Resources:
    + 3 to create

Nothing has been created yet. The preview is free and safe to run as many times as you want. Once you are happy with the plan:

pulumi up

Pulumi shows the same preview and asks for confirmation. Select yes and press Enter:

Updating (dev):

     Type                  Name               Status
 +   pulumi:pulumi:Stack   hetzner-pulumi-intro-dev  created
 +   ├─ pulumi:providers:hcloud  hcloud         created
 +   └─ hcloud:index:Server      my-first-server  created

Outputs:
    serverIp  : "203.0.113.10"
    serverName: "my-first-server"

Resources:
    + 3 created

Duration: 12s

Your server is running. The public IP appears in the outputs.

Connecting to your server

If you did not add an SSH key, Hetzner sends the root password to your account email automatically. Connect using:

ssh root@$(pulumi stack output serverIp)
# enter the password from your email

For production servers, always use SSH keys instead of passwords. The next tutorials in this series show how to add an SSH key to your Pulumi configuration.

Step 6 - Inspect the stack

At any point you can check what Pulumi knows about your infrastructure:

# See all outputs
pulumi stack output

# See a specific output
pulumi stack output serverIp

You can also open the Hetzner Console and see the server there — Pulumi and the Console show the same thing because they both talk to the same Hetzner API.

Step 7 - Modify the infrastructure

This is where Pulumi starts to show its value. Change the label in your index.ts:

  labels: {
    managedBy: "pulumi",
    env:       "tutorial",
    updated:   "yes",          // add this line
  },

Run pulumi preview again:

~ update  hcloud:index:Server  my-first-server  [diff: ~labels]

Pulumi detected exactly what changed — only the labels, nothing else. Run pulumi up to apply it. The server is updated in place, no recreation needed.

This is the update cycle in practice: change the code, preview, apply. The state file tracks what exists so Pulumi only touches what changed.

Step 8 - Destroy the infrastructure

When you are done:

pulumi destroy

Pulumi shows what will be deleted and asks for confirmation. After confirming, the server is removed from Hetzner and you stop being billed for it.

pulumi stack rm dev

This removes the local stack state file.

Conclusion

You provisioned a Hetzner Cloud server using real TypeScript code. The workflow — write, preview, apply — is the same pattern you will use for everything from a single server to a full Kubernetes cluster.

A few things worth remembering from this tutorial:

  • pulumi preview is always safe — it shows the plan without making changes
  • Secrets are encrypted in stack config — never put tokens in your code
  • Exports make outputs accessible via pulumi stack output
  • The same language skills you already have apply directly to infrastructure

Full source code for this series: Getting Started with Pulumi and TypeScript on Hetzner Cloud

License: MIT
Want to contribute?

Get Rewarded: Get up to €50 in credit! Be a part of the community and contribute. Do it for the money. Do it for the bragging rights. And do it to teach others!

Report Issue
Try Hetzner Cloud

Get €20/$20 free credit!

Valid until: 31 December 2026 Valid for: 3 months and only for new customers
Get started
Want to contribute?

Get Rewarded: Get up to €50 credit on your account for every tutorial you write and we publish!

Find out more