diff --git a/LICENSE b/LICENSE
index 51d31cad15a369f9495558a774f6f07720e617b8..67b3aa0f5d561f39af20d247832cce51193fd6e5 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2017-2021 The docker-machine-driver-hetzner team
+Copyright (c) 2017-2023 The docker-machine-driver-hetzner team
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/driver.go b/driver.go
deleted file mode 100644
index f8ad6b2b9ac8c3e437a0f05ed17732282b8522f9..0000000000000000000000000000000000000000
--- a/driver.go
+++ /dev/null
@@ -1,1385 +0,0 @@
-package main
-
-import (
-	"context"
-	"fmt"
-	"net"
-	"os"
-	"strings"
-	"time"
-
-	"github.com/docker/machine/libmachine/drivers"
-	"github.com/docker/machine/libmachine/log"
-	"github.com/docker/machine/libmachine/mcnflag"
-	"github.com/docker/machine/libmachine/mcnutils"
-	mcnssh "github.com/docker/machine/libmachine/ssh"
-	"github.com/docker/machine/libmachine/state"
-	"github.com/hetznercloud/hcloud-go/hcloud"
-	"github.com/pkg/errors"
-	"golang.org/x/crypto/ssh"
-)
-
-// Driver contains hetzner-specific data to implement [drivers.Driver]
-type Driver struct {
-	*drivers.BaseDriver
-
-	AccessToken       string
-	Image             string
-	ImageID           int
-	ImageArch         hcloud.Architecture
-	cachedImage       *hcloud.Image
-	Type              string
-	cachedType        *hcloud.ServerType
-	Location          string
-	cachedLocation    *hcloud.Location
-	KeyID             int
-	cachedKey         *hcloud.SSHKey
-	IsExistingKey     bool
-	originalKey       string
-	dangling          []func()
-	ServerID          int
-	cachedServer      *hcloud.Server
-	userData          string
-	userDataFile      string
-	Volumes           []string
-	Networks          []string
-	UsePrivateNetwork bool
-	DisablePublic4    bool
-	DisablePublic6    bool
-	PrimaryIPv4       string
-	cachedPrimaryIPv4 *hcloud.PrimaryIP
-	PrimaryIPv6       string
-	cachedPrimaryIPv6 *hcloud.PrimaryIP
-	Firewalls         []string
-	ServerLabels      map[string]string
-	keyLabels         map[string]string
-	placementGroup    string
-	cachedPGrp        *hcloud.PlacementGroup
-
-	AdditionalKeys       []string
-	AdditionalKeyIDs     []int
-	cachedAdditionalKeys []*hcloud.SSHKey
-
-	WaitOnError int
-}
-
-const (
-	defaultImage = "ubuntu-18.04"
-	defaultType  = "cx11"
-
-	flagAPIToken          = "hetzner-api-token"
-	flagImage             = "hetzner-image"
-	flagImageID           = "hetzner-image-id"
-	flagImageArch         = "hetzner-image-arch"
-	flagType              = "hetzner-server-type"
-	flagLocation          = "hetzner-server-location"
-	flagExKeyID           = "hetzner-existing-key-id"
-	flagExKeyPath         = "hetzner-existing-key-path"
-	flagUserData          = "hetzner-user-data"
-	flagUserDataFile      = "hetzner-user-data-file"
-	flagVolumes           = "hetzner-volumes"
-	flagNetworks          = "hetzner-networks"
-	flagUsePrivateNetwork = "hetzner-use-private-network"
-	flagDisablePublic4    = "hetzner-disable-public-ipv4"
-	flagDisablePublic6    = "hetzner-disable-public-ipv6"
-	flagPrimary4          = "hetzner-primary-ipv4"
-	flagPrimary6          = "hetzner-primary-ipv6"
-	flagDisablePublic     = "hetzner-disable-public"
-	flagFirewalls         = "hetzner-firewalls"
-	flagAdditionalKeys    = "hetzner-additional-key"
-	flagServerLabel       = "hetzner-server-label"
-	flagKeyLabel          = "hetzner-key-label"
-	flagPlacementGroup    = "hetzner-placement-group"
-	flagAutoSpread        = "hetzner-auto-spread"
-
-	flagSshUser = "hetzner-ssh-user"
-	flagSshPort = "hetzner-ssh-port"
-
-	labelNamespace    = "docker-machine"
-	labelAutoSpreadPg = "auto-spread"
-	labelAutoCreated  = "auto-created"
-
-	autoSpreadPgName = "__auto_spread"
-
-	defaultSSHPort = 22
-	defaultSSHUser = "root"
-
-	flagWaitOnError    = "hetzner-wait-on-error"
-	defaultWaitOnError = 0
-
-	legacyFlagUserDataFromFile = "hetzner-user-data-from-file"
-	legacyFlagDisablePublic4   = "hetzner-disable-public-4"
-	legacyFlagDisablePublic6   = "hetzner-disable-public-6"
-
-	emptyImageArchitecture = hcloud.Architecture("")
-)
-
-// NewDriver initializes a new driver instance; see [drivers.Driver.NewDriver]
-func NewDriver() *Driver {
-	return &Driver{
-		Type:          defaultType,
-		IsExistingKey: false,
-		BaseDriver:    &drivers.BaseDriver{},
-	}
-}
-
-// DriverName returns the hard-coded string "hetzner"; see [drivers.Driver.DriverName]
-func (d *Driver) DriverName() string {
-	return "hetzner"
-}
-
-// GetCreateFlags retrieves additional driver-specific arguments; see [drivers.Driver.GetCreateFlags]
-func (d *Driver) GetCreateFlags() []mcnflag.Flag {
-	return []mcnflag.Flag{
-		mcnflag.StringFlag{
-			EnvVar: "HETZNER_API_TOKEN",
-			Name:   flagAPIToken,
-			Usage:  "Project-specific Hetzner API token",
-			Value:  "",
-		},
-		mcnflag.StringFlag{
-			EnvVar: "HETZNER_IMAGE",
-			Name:   flagImage,
-			Usage:  "Image to use for server creation",
-			Value:  "",
-		},
-		mcnflag.IntFlag{
-			EnvVar: "HETZNER_IMAGE_ID",
-			Name:   flagImageID,
-			Usage:  "Image to use for server creation",
-		},
-		mcnflag.StringFlag{
-			EnvVar: "HETZNER_IMAGE_ARCH",
-			Name:   flagImageArch,
-			Usage:  "Image architecture for lookup to use for server creation",
-		},
-		mcnflag.StringFlag{
-			EnvVar: "HETZNER_TYPE",
-			Name:   flagType,
-			Usage:  "Server type to create",
-			Value:  defaultType,
-		},
-		mcnflag.StringFlag{
-			EnvVar: "HETZNER_LOCATION",
-			Name:   flagLocation,
-			Usage:  "Location to create machine at",
-			Value:  "",
-		},
-		mcnflag.IntFlag{
-			EnvVar: "HETZNER_EXISTING_KEY_ID",
-			Name:   flagExKeyID,
-			Usage:  "Existing key ID to use for server; requires --hetzner-existing-key-path",
-			Value:  0,
-		},
-		mcnflag.StringFlag{
-			EnvVar: "HETZNER_EXISTING_KEY_PATH",
-			Name:   flagExKeyPath,
-			Usage:  "Path to existing key (new public key will be created unless --hetzner-existing-key-id is specified)",
-			Value:  "",
-		},
-		mcnflag.StringFlag{
-			EnvVar: "HETZNER_USER_DATA",
-			Name:   flagUserData,
-			Usage:  "Cloud-init based user data (inline).",
-			Value:  "",
-		},
-		mcnflag.BoolFlag{
-			EnvVar: "HETZNER_USER_DATA_FROM_FILE",
-			Name:   legacyFlagUserDataFromFile,
-			Usage:  "DEPRECATED, use --hetzner-user-data-file. Treat --hetzner-user-data argument as filename.",
-		},
-		mcnflag.StringFlag{
-			EnvVar: "HETZNER_USER_DATA_FILE",
-			Name:   flagUserDataFile,
-			Usage:  "Cloud-init based user data (read from file)",
-			Value:  "",
-		},
-		mcnflag.StringSliceFlag{
-			EnvVar: "HETZNER_VOLUMES",
-			Name:   flagVolumes,
-			Usage:  "Volume IDs or names which should be attached to the server",
-			Value:  []string{},
-		},
-		mcnflag.StringSliceFlag{
-			EnvVar: "HETZNER_NETWORKS",
-			Name:   flagNetworks,
-			Usage:  "Network IDs or names which should be attached to the server private network interface",
-			Value:  []string{},
-		},
-		mcnflag.BoolFlag{
-			EnvVar: "HETZNER_USE_PRIVATE_NETWORK",
-			Name:   flagUsePrivateNetwork,
-			Usage:  "Use private network",
-		},
-		mcnflag.BoolFlag{
-			EnvVar: "HETZNER_DISABLE_PUBLIC_IPV4",
-			Name:   flagDisablePublic4,
-			Usage:  "Disable public ipv4",
-		},
-		mcnflag.BoolFlag{
-			EnvVar: "HETZNER_DISABLE_PUBLIC_4",
-			Name:   legacyFlagDisablePublic4,
-			Usage:  "DEPRECATED, use --hetzner-disable-public-ipv4; disable public ipv4",
-		},
-		mcnflag.BoolFlag{
-			EnvVar: "HETZNER_DISABLE_PUBLIC_IPV6",
-			Name:   flagDisablePublic6,
-			Usage:  "Disable public ipv6",
-		},
-		mcnflag.BoolFlag{
-			EnvVar: "HETZNER_DISABLE_PUBLIC_6",
-			Name:   legacyFlagDisablePublic6,
-			Usage:  "DEPRECATED, use --hetzner-disable-public-ipv6; disable public ipv6",
-		},
-		mcnflag.BoolFlag{
-			EnvVar: "HETZNER_DISABLE_PUBLIC",
-			Name:   flagDisablePublic,
-			Usage:  "Disable public ip (v4 & v6)",
-		},
-		mcnflag.StringFlag{
-			EnvVar: "HETZNER_PRIMARY_IPV4",
-			Name:   flagPrimary4,
-			Usage:  "Existing primary IPv4 address",
-			Value:  "",
-		},
-		mcnflag.StringFlag{
-			EnvVar: "HETZNER_PRIMARY_IPV6",
-			Name:   flagPrimary6,
-			Usage:  "Existing primary IPv6 address",
-			Value:  "",
-		},
-		mcnflag.StringSliceFlag{
-			EnvVar: "HETZNER_FIREWALLS",
-			Name:   flagFirewalls,
-			Usage:  "Firewall IDs or names which should be applied on the server",
-			Value:  []string{},
-		},
-		mcnflag.StringSliceFlag{
-			EnvVar: "HETZNER_ADDITIONAL_KEYS",
-			Name:   flagAdditionalKeys,
-			Usage:  "Additional public keys to be attached to the server",
-			Value:  []string{},
-		},
-		mcnflag.StringSliceFlag{
-			EnvVar: "HETZNER_SERVER_LABELS",
-			Name:   flagServerLabel,
-			Usage:  "Key value pairs of additional labels to assign to the server",
-			Value:  []string{},
-		},
-		mcnflag.StringSliceFlag{
-			EnvVar: "HETZNER_KEY_LABELS",
-			Name:   flagKeyLabel,
-			Usage:  "Key value pairs of additional labels to assign to the SSH key",
-			Value:  []string{},
-		},
-		mcnflag.StringFlag{
-			EnvVar: "HETZNER_PLACEMENT_GROUP",
-			Name:   flagPlacementGroup,
-			Usage:  "Placement group ID or name to add the server to; will be created if it does not exist",
-			Value:  "",
-		},
-		mcnflag.BoolFlag{
-			EnvVar: "HETZNER_AUTO_SPREAD",
-			Name:   flagAutoSpread,
-			Usage:  "Auto-spread on a docker-machine-specific default placement group",
-		},
-		mcnflag.StringFlag{
-			EnvVar: "HETZNER_SSH_USER",
-			Name:   flagSshUser,
-			Usage:  "SSH username",
-			Value:  defaultSSHUser,
-		},
-		mcnflag.IntFlag{
-			EnvVar: "HETZNER_SSH_PORT",
-			Name:   flagSshPort,
-			Usage:  "SSH port",
-			Value:  defaultSSHPort,
-		},
-		mcnflag.IntFlag{
-			EnvVar: "HETZNER_WAIT_ON_ERROR",
-			Name:   flagWaitOnError,
-			Usage:  "Wait if an error happens while creating the server",
-			Value:  defaultWaitOnError,
-		},
-	}
-}
-
-// SetConfigFromFlags handles additional driver arguments as retrieved by [Driver.GetCreateFlags];
-// see [drivers.Driver.SetConfigFromFlags]
-func (d *Driver) SetConfigFromFlags(opts drivers.DriverOptions) error {
-	return d.setConfigFromFlags(opts)
-}
-
-func (d *Driver) setConfigFromFlagsImpl(opts drivers.DriverOptions) error {
-	d.AccessToken = opts.String(flagAPIToken)
-	d.Image = opts.String(flagImage)
-	d.ImageID = opts.Int(flagImageID)
-	err := d.setImageArch(opts.String(flagImageArch))
-	if err != nil {
-		return err
-	}
-	d.Location = opts.String(flagLocation)
-	d.Type = opts.String(flagType)
-	d.KeyID = opts.Int(flagExKeyID)
-	d.IsExistingKey = d.KeyID != 0
-	d.originalKey = opts.String(flagExKeyPath)
-	err = d.setUserDataFlags(opts)
-	if err != nil {
-		return err
-	}
-	d.Volumes = opts.StringSlice(flagVolumes)
-	d.Networks = opts.StringSlice(flagNetworks)
-	disablePublic := opts.Bool(flagDisablePublic)
-	d.UsePrivateNetwork = opts.Bool(flagUsePrivateNetwork) || disablePublic
-	d.DisablePublic4 = d.deprecatedBooleanFlag(opts, flagDisablePublic4, legacyFlagDisablePublic4) || disablePublic
-	d.DisablePublic6 = d.deprecatedBooleanFlag(opts, flagDisablePublic6, legacyFlagDisablePublic6) || disablePublic
-	d.PrimaryIPv4 = opts.String(flagPrimary4)
-	d.PrimaryIPv6 = opts.String(flagPrimary6)
-	d.Firewalls = opts.StringSlice(flagFirewalls)
-	d.AdditionalKeys = opts.StringSlice(flagAdditionalKeys)
-
-	d.SSHUser = opts.String(flagSshUser)
-	d.SSHPort = opts.Int(flagSshPort)
-
-	d.WaitOnError = opts.Int(flagWaitOnError)
-
-	d.placementGroup = opts.String(flagPlacementGroup)
-	if opts.Bool(flagAutoSpread) {
-		if d.placementGroup != "" {
-			return d.flagFailure("%v and %v are mutually exclusive", flagAutoSpread, flagPlacementGroup)
-		}
-		d.placementGroup = autoSpreadPgName
-	}
-
-	err = d.setLabelsFromFlags(opts)
-	if err != nil {
-		return err
-	}
-
-	d.SetSwarmConfigFromFlags(opts)
-
-	if d.AccessToken == "" {
-		return d.flagFailure("hetzner requires --%v to be set", flagAPIToken)
-	}
-
-	if err = d.verifyImageFlags(); err != nil {
-		return err
-	}
-
-	if err = d.verifyNetworkFlags(); err != nil {
-		return err
-	}
-
-	instrumented(d)
-
-	return nil
-}
-
-func (d *Driver) setImageArch(arch string) error {
-	switch arch {
-	case "":
-		d.ImageArch = emptyImageArchitecture
-	case string(hcloud.ArchitectureARM):
-		d.ImageArch = hcloud.ArchitectureARM
-	case string(hcloud.ArchitectureX86):
-		d.ImageArch = hcloud.ArchitectureX86
-	default:
-		return errors.Errorf("unknown architecture %v", arch)
-	}
-	return nil
-}
-
-func (d *Driver) verifyImageFlags() error {
-	if d.ImageID != 0 && d.Image != "" && d.Image != defaultImage /* support legacy behaviour */ {
-		return d.flagFailure("--%v and --%v are mutually exclusive", flagImage, flagImageID)
-	} else if d.ImageID != 0 && d.ImageArch != "" {
-		return d.flagFailure("--%v and --%v are mutually exclusive", flagImageArch, flagImageID)
-	} else if d.ImageID == 0 && d.Image == "" {
-		d.Image = defaultImage
-	}
-	return nil
-}
-
-func (d *Driver) verifyNetworkFlags() error {
-	if d.DisablePublic4 && d.DisablePublic6 && !d.UsePrivateNetwork {
-		return d.flagFailure("--%v must be used if public networking is disabled (hint: implicitly set by --%v)",
-			flagUsePrivateNetwork, flagDisablePublic)
-	}
-
-	if d.DisablePublic4 && d.PrimaryIPv4 != "" {
-		return d.flagFailure("--%v and --%v are mutually exclusive", flagPrimary4, flagDisablePublic4)
-	}
-
-	if d.DisablePublic6 && d.PrimaryIPv6 != "" {
-		return d.flagFailure("--%v and --%v are mutually exclusive", flagPrimary6, flagDisablePublic6)
-	}
-	return nil
-}
-
-func (d *Driver) deprecatedBooleanFlag(opts drivers.DriverOptions, flag, deprecatedFlag string) bool {
-	if opts.Bool(deprecatedFlag) {
-		log.Warnf("--%v is deprecated, use --%v instead", deprecatedFlag, flag)
-		return true
-	}
-	return opts.Bool(flag)
-}
-
-func (d *Driver) setUserDataFlags(opts drivers.DriverOptions) error {
-	userData := opts.String(flagUserData)
-	userDataFile := opts.String(flagUserDataFile)
-
-	if opts.Bool(legacyFlagUserDataFromFile) {
-		if userDataFile != "" {
-			return d.flagFailure("--%v and --%v are mutually exclusive", flagUserDataFile, legacyFlagUserDataFromFile)
-		}
-
-		log.Warnf("--%v is deprecated, pass '--%v \"%v\"'", legacyFlagUserDataFromFile, flagUserDataFile, userData)
-		d.userDataFile = userData
-		return nil
-	}
-
-	d.userData = userData
-	d.userDataFile = userDataFile
-
-	if d.userData != "" && d.userDataFile != "" {
-		return d.flagFailure("--%v and --%v are mutually exclusive", flagUserData, flagUserDataFile)
-	}
-
-	return nil
-}
-
-// GetSSHUsername retrieves the SSH username used to connect to the server during provisioning
-func (d *Driver) GetSSHUsername() string {
-	return d.SSHUser
-}
-
-// GetSSHPort retrieves the port used to connect to the server during provisioning
-func (d *Driver) GetSSHPort() (int, error) {
-	return d.SSHPort, nil
-}
-
-func (d *Driver) setLabelsFromFlags(opts drivers.DriverOptions) error {
-	d.ServerLabels = make(map[string]string)
-	for _, label := range opts.StringSlice(flagServerLabel) {
-		split := strings.SplitN(label, "=", 2)
-		if len(split) != 2 {
-			return d.flagFailure("server label %v is not in key=value format", label)
-		}
-		d.ServerLabels[split[0]] = split[1]
-	}
-	d.keyLabels = make(map[string]string)
-	for _, label := range opts.StringSlice(flagKeyLabel) {
-		split := strings.SplitN(label, "=", 2)
-		if len(split) != 2 {
-			return errors.Errorf("key label %v is not in key=value format", label)
-		}
-		d.keyLabels[split[0]] = split[1]
-	}
-	return nil
-}
-
-// PreCreateCheck validates the Driver data is in a valid state for creation; see [drivers.Driver.PreCreateCheck]
-func (d *Driver) PreCreateCheck() error {
-	if err := d.setupExistingKey(); err != nil {
-		return err
-	}
-
-	if serverType, err := d.getType(); err != nil {
-		return errors.Wrap(err, "could not get type")
-	} else if d.ImageArch != "" && serverType.Architecture != d.ImageArch {
-		log.Warnf("supplied architecture %v differs from server architecture %v", d.ImageArch, serverType.Architecture)
-	}
-
-	if _, err := d.getImage(); err != nil {
-		return errors.Wrap(err, "could not get image")
-	}
-
-	if _, err := d.getLocation(); err != nil {
-		return errors.Wrap(err, "could not get location")
-	}
-
-	if _, err := d.getPlacementGroup(); err != nil {
-		return fmt.Errorf("could not create placement group: %w", err)
-	}
-
-	if _, err := d.getPrimaryIPv4(); err != nil {
-		return fmt.Errorf("could not resolve primary IPv4: %w", err)
-	}
-
-	if _, err := d.getPrimaryIPv6(); err != nil {
-		return fmt.Errorf("could not resolve primary IPv6: %w", err)
-	}
-
-	if d.UsePrivateNetwork && len(d.Networks) == 0 {
-		return errors.Errorf("No private network attached.")
-	}
-
-	return nil
-}
-
-func (d *Driver) setupExistingKey() error {
-	if !d.IsExistingKey {
-		return nil
-	}
-
-	if d.originalKey == "" {
-		return d.flagFailure("specifying an existing key ID requires the existing key path to be set as well")
-	}
-
-	key, err := d.getKey()
-	if err != nil {
-		return errors.Wrap(err, "could not get key")
-	}
-
-	buf, err := os.ReadFile(d.originalKey + ".pub")
-	if err != nil {
-		return errors.Wrap(err, "could not read public key")
-	}
-
-	// Will also parse `ssh-rsa w309jwf0e39jf asdf` public keys
-	pubk, _, _, _, err := ssh.ParseAuthorizedKey(buf)
-	if err != nil {
-		return errors.Wrap(err, "could not parse authorized key")
-	}
-
-	if key.Fingerprint != ssh.FingerprintLegacyMD5(pubk) &&
-		key.Fingerprint != ssh.FingerprintSHA256(pubk) {
-		return errors.Errorf("remote key %d does not match local key %s", d.KeyID, d.originalKey)
-	}
-
-	return nil
-}
-
-// Create actually creates the hetzner-cloud server; see [drivers.Driver.Create]
-func (d *Driver) Create() error {
-	err := d.prepareLocalKey()
-	if err != nil {
-		return err
-	}
-
-	defer d.destroyDangling()
-	err = d.createRemoteKeys()
-	if err != nil {
-		return err
-	}
-
-	log.Infof("Creating Hetzner server...")
-
-	srvopts, err := d.makeCreateServerOptions()
-	if err != nil {
-		return err
-	}
-
-	srv, _, err := d.getClient().Server.Create(context.Background(), instrumented(*srvopts))
-	if err != nil {
-		time.Sleep(time.Duration(d.WaitOnError) * time.Second)
-		return errors.Wrap(err, "could not create server")
-	}
-
-	log.Infof(" -> Creating server %s[%d] in %s[%d]", srv.Server.Name, srv.Server.ID, srv.Action.Command, srv.Action.ID)
-	if err = d.waitForAction(srv.Action); err != nil {
-		return errors.Wrap(err, "could not wait for action")
-	}
-
-	d.ServerID = srv.Server.ID
-	log.Infof(" -> Server %s[%d]: Waiting to come up...", srv.Server.Name, srv.Server.ID)
-
-	err = d.waitForRunningServer()
-	if err != nil {
-		return err
-	}
-
-	err = d.configureNetworkAccess(srv)
-	if err != nil {
-		return err
-	}
-
-	log.Infof(" -> Server %s[%d] ready. Ip %s", srv.Server.Name, srv.Server.ID, d.IPAddress)
-	// Successful creation, so no keys dangle anymore
-	d.dangling = nil
-
-	return nil
-}
-
-func (d *Driver) configureNetworkAccess(srv hcloud.ServerCreateResult) error {
-	if d.UsePrivateNetwork {
-		for {
-			// we need to wait until network is attached
-			log.Infof("Wait until private network attached ...")
-			server, _, err := d.getClient().Server.GetByID(context.Background(), srv.Server.ID)
-			if err != nil {
-				return errors.Wrapf(err, "could not get newly created server [%d]", srv.Server.ID)
-			}
-			if server.PrivateNet != nil {
-				d.IPAddress = server.PrivateNet[0].IP.String()
-				break
-			}
-			time.Sleep(1 * time.Second)
-		}
-	} else if d.DisablePublic4 {
-		log.Infof("Using public IPv6 network ...")
-
-		pv6 := srv.Server.PublicNet.IPv6
-		ip := pv6.IP
-		if ip.Mask(pv6.Network.Mask).Equal(pv6.Network.IP) { // no host given
-			ip[net.IPv6len-1] |= 0x01 // TODO make this configurable
-		}
-
-		ips := ip.String()
-		log.Infof(" -> resolved %v ...", ips)
-		d.IPAddress = ips
-	} else {
-		log.Infof("Using public network ...")
-		d.IPAddress = srv.Server.PublicNet.IPv4.IP.String()
-	}
-	return nil
-}
-
-func (d *Driver) waitForRunningServer() error {
-	for {
-		srvstate, err := d.GetState()
-		if err != nil {
-			return errors.Wrap(err, "could not get state")
-		}
-
-		if srvstate == state.Running {
-			break
-		}
-
-		time.Sleep(1 * time.Second)
-	}
-	return nil
-}
-
-func (d *Driver) makeCreateServerOptions() (*hcloud.ServerCreateOpts, error) {
-	pgrp, err := d.getPlacementGroup()
-	if err != nil {
-		return nil, err
-	}
-
-	userData, err := d.getUserData()
-	if err != nil {
-		return nil, err
-	}
-
-	srvopts := hcloud.ServerCreateOpts{
-		Name:           d.GetMachineName(),
-		UserData:       userData,
-		Labels:         d.ServerLabels,
-		PlacementGroup: pgrp,
-	}
-
-	err = d.setPublicNetIfRequired(&srvopts)
-	if err != nil {
-		return nil, err
-	}
-
-	networks, err := d.createNetworks()
-	if err != nil {
-		return nil, err
-	}
-	srvopts.Networks = networks
-
-	firewalls, err := d.createFirewalls()
-	if err != nil {
-		return nil, err
-	}
-	srvopts.Firewalls = firewalls
-
-	volumes, err := d.createVolumes()
-	if err != nil {
-		return nil, err
-	}
-	srvopts.Volumes = volumes
-
-	if srvopts.Location, err = d.getLocation(); err != nil {
-		return nil, errors.Wrap(err, "could not get location")
-	}
-	if srvopts.ServerType, err = d.getType(); err != nil {
-		return nil, errors.Wrap(err, "could not get type")
-	}
-	if srvopts.Image, err = d.getImage(); err != nil {
-		return nil, errors.Wrap(err, "could not get image")
-	}
-	key, err := d.getKey()
-	if err != nil {
-		return nil, errors.Wrap(err, "could not get ssh key")
-	}
-	srvopts.SSHKeys = append(d.cachedAdditionalKeys, key)
-	return &srvopts, nil
-}
-
-func (d *Driver) getUserData() (string, error) {
-	file := d.userDataFile
-	if file == "" {
-		return d.userData, nil
-	}
-
-	readUserData, err := os.ReadFile(file)
-	if err != nil {
-		return "", err
-	}
-	return string(readUserData), nil
-}
-
-func (d *Driver) setPublicNetIfRequired(srvopts *hcloud.ServerCreateOpts) error {
-	pip4, err := d.getPrimaryIPv4()
-	if err != nil {
-		return err
-	}
-	pip6, err := d.getPrimaryIPv6()
-	if err != nil {
-		return err
-	}
-
-	if d.DisablePublic4 || d.DisablePublic6 || pip4 != nil || pip6 != nil {
-		srvopts.PublicNet = &hcloud.ServerCreatePublicNet{
-			EnableIPv4: !d.DisablePublic4 || pip4 != nil,
-			EnableIPv6: !d.DisablePublic6 || pip6 != nil,
-			IPv4:       pip4,
-			IPv6:       pip6,
-		}
-	}
-	return nil
-}
-
-func (d *Driver) createNetworks() ([]*hcloud.Network, error) {
-	networks := []*hcloud.Network{}
-	for _, networkIDorName := range d.Networks {
-		network, _, err := d.getClient().Network.Get(context.Background(), networkIDorName)
-		if err != nil {
-			return nil, errors.Wrap(err, "could not get network by ID or name")
-		}
-		if network == nil {
-			return nil, errors.Errorf("network '%s' not found", networkIDorName)
-		}
-		networks = append(networks, network)
-	}
-	return instrumented(networks), nil
-}
-
-func (d *Driver) createFirewalls() ([]*hcloud.ServerCreateFirewall, error) {
-	firewalls := []*hcloud.ServerCreateFirewall{}
-	for _, firewallIDorName := range d.Firewalls {
-		firewall, _, err := d.getClient().Firewall.Get(context.Background(), firewallIDorName)
-		if err != nil {
-			return nil, errors.Wrap(err, "could not get firewall by ID or name")
-		}
-		if firewall == nil {
-			return nil, errors.Errorf("firewall '%s' not found", firewallIDorName)
-		}
-		firewalls = append(firewalls, &hcloud.ServerCreateFirewall{Firewall: *firewall})
-	}
-	return instrumented(firewalls), nil
-}
-
-func (d *Driver) createVolumes() ([]*hcloud.Volume, error) {
-	volumes := []*hcloud.Volume{}
-	for _, volumeIDorName := range d.Volumes {
-		volume, _, err := d.getClient().Volume.Get(context.Background(), volumeIDorName)
-		if err != nil {
-			return nil, errors.Wrap(err, "could not get volume by ID or name")
-		}
-		if volume == nil {
-			return nil, errors.Errorf("volume '%s' not found", volumeIDorName)
-		}
-		volumes = append(volumes, volume)
-	}
-	return instrumented(volumes), nil
-}
-
-func (d *Driver) createRemoteKeys() error {
-	if d.KeyID == 0 {
-		log.Infof("Creating SSH key...")
-
-		buf, err := os.ReadFile(d.GetSSHKeyPath() + ".pub")
-		if err != nil {
-			return errors.Wrap(err, "could not read ssh public key")
-		}
-
-		key, err := d.getRemoteKeyWithSameFingerprint(buf)
-		if err != nil {
-			return errors.Wrap(err, "error retrieving potentially existing key")
-		}
-		if key == nil {
-			log.Infof("SSH key not found in Hetzner. Uploading...")
-
-			key, err = d.makeKey(d.GetMachineName(), string(buf), d.keyLabels)
-			if err != nil {
-				return err
-			}
-		} else {
-			d.IsExistingKey = true
-			log.Debugf("SSH key found in Hetzner. ID: %d", key.ID)
-		}
-
-		d.KeyID = key.ID
-	}
-	for i, pubkey := range d.AdditionalKeys {
-		key, err := d.getRemoteKeyWithSameFingerprint([]byte(pubkey))
-		if err != nil {
-			return errors.Wrapf(err, "error checking for existing key for %v", pubkey)
-		}
-		if key == nil {
-			log.Infof("Creating new key for %v...", pubkey)
-			key, err = d.makeKey(fmt.Sprintf("%v-additional-%d", d.GetMachineName(), i), pubkey, d.keyLabels)
-
-			if err != nil {
-				return errors.Wrapf(err, "error creating new key for %v", pubkey)
-			}
-
-			log.Infof(" -> Created %v", key.ID)
-			d.AdditionalKeyIDs = append(d.AdditionalKeyIDs, key.ID)
-		} else {
-			log.Infof("Using existing key (%v) %v", key.ID, key.Name)
-		}
-
-		d.cachedAdditionalKeys = append(d.cachedAdditionalKeys, key)
-	}
-	return nil
-}
-
-func (d *Driver) prepareLocalKey() error {
-	if d.originalKey != "" {
-		log.Debugf("Copying SSH key...")
-		if err := d.copySSHKeyPair(d.originalKey); err != nil {
-			return errors.Wrap(err, "could not copy ssh key pair")
-		}
-	} else {
-		log.Debugf("Generating SSH key...")
-		if err := mcnssh.GenerateSSHKey(d.GetSSHKeyPath()); err != nil {
-			return errors.Wrap(err, "could not generate ssh key")
-		}
-	}
-	return nil
-}
-
-// Creates a new key for the machine and appends it to the dangling key list
-func (d *Driver) makeKey(name string, pubkey string, labels map[string]string) (*hcloud.SSHKey, error) {
-	keyopts := hcloud.SSHKeyCreateOpts{
-		Name:      name,
-		PublicKey: pubkey,
-		Labels:    labels,
-	}
-
-	key, _, err := d.getClient().SSHKey.Create(context.Background(), instrumented(keyopts))
-	if err != nil {
-		return nil, errors.Wrap(err, "could not create ssh key")
-	} else if key == nil {
-		return nil, errors.Errorf("key upload did not return an error, but key was nil")
-	}
-
-	d.dangling = append(d.dangling, func() {
-		_, err := d.getClient().SSHKey.Delete(context.Background(), key)
-		if err != nil {
-			log.Error(fmt.Errorf("could not delete ssh key: %w", err))
-		}
-	})
-
-	return key, nil
-}
-
-func (d *Driver) destroyDangling() {
-	for _, destructor := range d.dangling {
-		destructor()
-	}
-}
-
-// GetSSHHostname retrieves the SSH host to connect to the machine; see [drivers.Driver.GetSSHHostname]
-func (d *Driver) GetSSHHostname() (string, error) {
-	return d.GetIP()
-}
-
-// GetURL retrieves the URL of the docker daemon on the machine; see [drivers.Driver.GetURL]
-func (d *Driver) GetURL() (string, error) {
-	if err := drivers.MustBeRunning(d); err != nil {
-		return "", errors.Wrap(err, "could not execute drivers.MustBeRunning")
-	}
-
-	ip, err := d.GetIP()
-	if err != nil {
-		return "", errors.Wrap(err, "could not get IP")
-	}
-
-	return fmt.Sprintf("tcp://%s", net.JoinHostPort(ip, "2376")), nil
-}
-
-// GetState retrieves the state the machine is currently in; see [drivers.Driver.GetState]
-func (d *Driver) GetState() (state.State, error) {
-	srv, _, err := d.getClient().Server.GetByID(context.Background(), d.ServerID)
-	if err != nil {
-		return state.None, errors.Wrap(err, "could not get server by ID")
-	}
-	if srv == nil {
-		return state.None, errors.New("server not found")
-	}
-
-	switch srv.Status {
-	case hcloud.ServerStatusInitializing:
-		return state.Starting, nil
-	case hcloud.ServerStatusRunning:
-		return state.Running, nil
-	case hcloud.ServerStatusOff:
-		return state.Stopped, nil
-	}
-	return state.None, nil
-}
-
-// Remove deletes the hetzner server and additional resources created during creation; see [drivers.Driver.Remove]
-func (d *Driver) Remove() error {
-	if err := d.destroyServer(); err != nil {
-		return err
-	}
-
-	// failure to remove a key is not ha hard error
-	for i, id := range d.AdditionalKeyIDs {
-		log.Infof(" -> Destroying additional key #%d (%d)", i, id)
-		key, _, softErr := d.getClient().SSHKey.GetByID(context.Background(), id)
-		if softErr != nil {
-			log.Warnf(" ->  -> could not retrieve key %v", softErr)
-		} else if key == nil {
-			log.Warnf(" ->  -> %d no longer exists", id)
-		}
-
-		_, softErr = d.getClient().SSHKey.Delete(context.Background(), key)
-		if softErr != nil {
-			log.Warnf(" ->  -> could not remove key: %v", softErr)
-		}
-	}
-
-	// failure to remove a server-specific key is a hard error
-	if !d.IsExistingKey && d.KeyID != 0 {
-		key, err := d.getKey()
-		if err != nil {
-			return errors.Wrap(err, "could not get ssh key")
-		}
-		if key == nil {
-			log.Infof(" -> SSH key does not exist anymore")
-			return nil
-		}
-
-		log.Infof(" -> Destroying SSHKey %s[%d]...", key.Name, key.ID)
-
-		if _, err := d.getClient().SSHKey.Delete(context.Background(), key); err != nil {
-			return errors.Wrap(err, "could not delete ssh key")
-		}
-	}
-
-	return nil
-}
-
-func (d *Driver) destroyServer() error {
-	if d.ServerID == 0 {
-		return nil
-	}
-
-	srv, err := d.getServerHandle()
-	if err != nil {
-		return errors.Wrap(err, "could not get server handle")
-	}
-
-	if srv == nil {
-		log.Infof(" -> Server does not exist anymore")
-	} else {
-		log.Infof(" -> Destroying server %s[%d] in...", srv.Name, srv.ID)
-
-		res, _, err := d.getClient().Server.DeleteWithResult(context.Background(), srv)
-		if err != nil {
-			return errors.Wrap(err, "could not delete server")
-		}
-
-		// failure to remove a placement group is not a hard error
-		if softErr := d.removeEmptyServerPlacementGroup(srv); softErr != nil {
-			log.Error(softErr)
-		}
-
-		// wait for the server to actually be deleted
-		if err = d.waitForAction(res.Action); err != nil {
-			return errors.Wrap(err, "could not wait for deletion")
-		}
-	}
-
-	return nil
-}
-
-// Restart instructs the hetzner cloud server to reboot; see [drivers.Driver.Restart]
-func (d *Driver) Restart() error {
-	srv, err := d.getServerHandle()
-	if err != nil {
-		return errors.Wrap(err, "could not get server handle")
-	}
-	if srv == nil {
-		return errors.New("server not found")
-	}
-
-	act, _, err := d.getClient().Server.Reboot(context.Background(), srv)
-	if err != nil {
-		return errors.Wrap(err, "could not reboot server")
-	}
-
-	log.Infof(" -> Rebooting server %s[%d] in %s[%d]...", srv.Name, srv.ID, act.Command, act.ID)
-
-	return d.waitForAction(act)
-}
-
-// Start instructs the hetzner cloud server to power up; see [drivers.Driver.Start]
-func (d *Driver) Start() error {
-	srv, err := d.getServerHandle()
-	if err != nil {
-		return errors.Wrap(err, "could not get server handle")
-	}
-	if srv == nil {
-		return errors.New("server not found")
-	}
-
-	act, _, err := d.getClient().Server.Poweron(context.Background(), srv)
-	if err != nil {
-		return errors.Wrap(err, "could not power on server")
-	}
-
-	log.Infof(" -> Starting server %s[%d] in %s[%d]...", srv.Name, srv.ID, act.Command, act.ID)
-
-	return d.waitForAction(act)
-}
-
-// Stop instructs the hetzner cloud server to shut down; see [drivers.Driver.Stop]
-func (d *Driver) Stop() error {
-	srv, err := d.getServerHandle()
-	if err != nil {
-		return errors.Wrap(err, "could not get server handle")
-	}
-	if srv == nil {
-		return errors.New("server not found")
-	}
-
-	act, _, err := d.getClient().Server.Shutdown(context.Background(), srv)
-	if err != nil {
-		return errors.Wrap(err, "could not shutdown server")
-	}
-
-	log.Infof(" -> Shutting down server %s[%d] in %s[%d]...", srv.Name, srv.ID, act.Command, act.ID)
-
-	return d.waitForAction(act)
-}
-
-// Kill forcefully shuts down the hetzner cloud server; see [drivers.Driver.Kill]
-func (d *Driver) Kill() error {
-	srv, err := d.getServerHandle()
-	if err != nil {
-		return errors.Wrap(err, "could not get server handle")
-	}
-	if srv == nil {
-		return errors.New("server not found")
-	}
-
-	act, _, err := d.getClient().Server.Poweroff(context.Background(), srv)
-	if err != nil {
-		return errors.Wrap(err, "could not poweroff server")
-	}
-
-	log.Infof(" -> Powering off server %s[%d] in %s[%d]...", srv.Name, srv.ID, act.Command, act.ID)
-
-	return d.waitForAction(act)
-}
-
-func (d *Driver) getClient() *hcloud.Client {
-	return hcloud.NewClient(hcloud.WithToken(d.AccessToken), hcloud.WithApplication("docker-machine-driver", version))
-}
-
-func (d *Driver) copySSHKeyPair(src string) error {
-	if err := mcnutils.CopyFile(src, d.GetSSHKeyPath()); err != nil {
-		return errors.Wrap(err, "could not copy ssh key")
-	}
-
-	if err := mcnutils.CopyFile(src+".pub", d.GetSSHKeyPath()+".pub"); err != nil {
-		return errors.Wrap(err, "could not copy ssh public key")
-	}
-
-	if err := os.Chmod(d.GetSSHKeyPath(), 0600); err != nil {
-		return errors.Wrap(err, "could not set permissions on the ssh key")
-	}
-
-	return nil
-}
-
-func (d *Driver) getLocation() (*hcloud.Location, error) {
-	if d.cachedLocation != nil {
-		return d.cachedLocation, nil
-	}
-
-	location, _, err := d.getClient().Location.GetByName(context.Background(), d.Location)
-	if err != nil {
-		return location, errors.Wrap(err, "could not get location by name")
-	}
-	d.cachedLocation = location
-	return location, nil
-}
-
-func (d *Driver) getType() (*hcloud.ServerType, error) {
-	if d.cachedType != nil {
-		return d.cachedType, nil
-	}
-
-	stype, _, err := d.getClient().ServerType.GetByName(context.Background(), d.Type)
-	if err != nil {
-		return stype, errors.Wrap(err, "could not get type by name")
-	}
-	d.cachedType = stype
-	return instrumented(stype), nil
-}
-
-func (d *Driver) getImage() (*hcloud.Image, error) {
-	if d.cachedImage != nil {
-		return d.cachedImage, nil
-	}
-
-	var image *hcloud.Image
-	var err error
-
-	if d.ImageID != 0 {
-		image, _, err = d.getClient().Image.GetByID(context.Background(), d.ImageID)
-		if err != nil {
-			return image, errors.Wrap(err, fmt.Sprintf("could not get image by id %v", d.ImageID))
-		}
-	} else {
-		arch, err := d.getImageArchitectureForLookup()
-		if err != nil {
-			return nil, errors.Wrap(err, "could not determine image architecture")
-		}
-
-		image, _, err = d.getClient().Image.GetByNameAndArchitecture(context.Background(), d.Image, arch)
-		if err != nil {
-			return image, errors.Wrap(err, fmt.Sprintf("could not get image by name %v", d.Image))
-		}
-	}
-
-	d.cachedImage = image
-	return instrumented(image), nil
-}
-
-func (d *Driver) getImageArchitectureForLookup() (hcloud.Architecture, error) {
-	if d.ImageArch != emptyImageArchitecture {
-		return d.ImageArch, nil
-	}
-
-	serverType, err := d.getType()
-	if err != nil {
-		return "", err
-	}
-
-	return serverType.Architecture, nil
-}
-
-func (d *Driver) getKey() (*hcloud.SSHKey, error) {
-	if d.cachedKey != nil {
-		return d.cachedKey, nil
-	}
-
-	stype, _, err := d.getClient().SSHKey.GetByID(context.Background(), d.KeyID)
-	if err != nil {
-		return stype, errors.Wrap(err, "could not get sshkey by ID")
-	}
-	d.cachedKey = stype
-	return instrumented(stype), nil
-}
-
-func (d *Driver) getRemoteKeyWithSameFingerprint(publicKeyBytes []byte) (*hcloud.SSHKey, error) {
-	publicKey, _, _, _, err := ssh.ParseAuthorizedKey(publicKeyBytes)
-	if err != nil {
-		return nil, errors.Wrap(err, "could not parse ssh public key")
-	}
-
-	fp := ssh.FingerprintLegacyMD5(publicKey)
-
-	remoteKey, _, err := d.getClient().SSHKey.GetByFingerprint(context.Background(), fp)
-	if err != nil {
-		return remoteKey, errors.Wrap(err, "could not get sshkey by fingerprint")
-	}
-	return instrumented(remoteKey), nil
-}
-
-func (d *Driver) getServerHandle() (*hcloud.Server, error) {
-	if d.cachedServer != nil {
-		return d.cachedServer, nil
-	}
-
-	if d.ServerID == 0 {
-		return nil, errors.New("server ID was 0")
-	}
-
-	srv, _, err := d.getClient().Server.GetByID(context.Background(), d.ServerID)
-	if err != nil {
-		return nil, errors.Wrap(err, "could not get client by ID")
-	}
-
-	d.cachedServer = srv
-	return srv, nil
-}
-
-func (d *Driver) waitForAction(a *hcloud.Action) error {
-	for {
-		act, _, err := d.getClient().Action.GetByID(context.Background(), a.ID)
-		if err != nil {
-			return errors.Wrap(err, "could not get client by ID")
-		}
-
-		if act.Status == hcloud.ActionStatusSuccess {
-			log.Debugf(" -> finished %s[%d]", act.Command, act.ID)
-			break
-		} else if act.Status == hcloud.ActionStatusRunning {
-			log.Debugf(" -> %s[%d]: %d %%", act.Command, act.ID, act.Progress)
-		} else if act.Status == hcloud.ActionStatusError {
-			return act.Error()
-		}
-
-		time.Sleep(1 * time.Second)
-	}
-	return nil
-}
-
-func (d *Driver) labelName(name string) string {
-	return labelNamespace + "/" + name
-}
-
-func (d *Driver) getAutoPlacementGroup() (*hcloud.PlacementGroup, error) {
-	res, err := d.getClient().PlacementGroup.AllWithOpts(context.Background(), hcloud.PlacementGroupListOpts{
-		ListOpts: hcloud.ListOpts{LabelSelector: d.labelName(labelAutoSpreadPg)},
-	})
-
-	if err != nil {
-		return nil, err
-	}
-
-	if len(res) != 0 {
-		return res[0], nil
-	}
-
-	grp, err := d.makePlacementGroup("Docker-Machine auto spread", map[string]string{
-		d.labelName(labelAutoSpreadPg): "true",
-		d.labelName(labelAutoCreated):  "true",
-	})
-
-	return instrumented(grp), err
-}
-
-func (d *Driver) makePlacementGroup(name string, labels map[string]string) (*hcloud.PlacementGroup, error) {
-	grp, _, err := d.getClient().PlacementGroup.Create(context.Background(), instrumented(hcloud.PlacementGroupCreateOpts{
-		Name:   name,
-		Labels: labels,
-		Type:   "spread",
-	}))
-
-	if grp.PlacementGroup != nil {
-		d.dangling = append(d.dangling, func() {
-			_, err := d.getClient().PlacementGroup.Delete(context.Background(), grp.PlacementGroup)
-			if err != nil {
-				log.Errorf("could not delete placement group: %v", err)
-			}
-		})
-	}
-
-	if err != nil {
-		return nil, fmt.Errorf("could not create placement group: %w", err)
-	}
-
-	return instrumented(grp.PlacementGroup), nil
-}
-
-func (d *Driver) getPlacementGroup() (*hcloud.PlacementGroup, error) {
-	if d.placementGroup == "" {
-		return nil, nil
-	} else if d.cachedPGrp != nil {
-		return d.cachedPGrp, nil
-	}
-
-	name := d.placementGroup
-	if name == autoSpreadPgName {
-		grp, err := d.getAutoPlacementGroup()
-		d.cachedPGrp = grp
-		return grp, err
-	} else {
-		client := d.getClient().PlacementGroup
-		grp, _, err := client.Get(context.Background(), name)
-		if err != nil {
-			return nil, fmt.Errorf("could not get placement group: %w", err)
-		}
-
-		if grp != nil {
-			return grp, nil
-		}
-
-		return d.makePlacementGroup(name, map[string]string{d.labelName(labelAutoCreated): "true"})
-	}
-}
-
-func (d *Driver) removeEmptyServerPlacementGroup(srv *hcloud.Server) error {
-	pg := srv.PlacementGroup
-	if pg == nil {
-		return nil
-	}
-
-	if len(pg.Servers) > 1 {
-		log.Debugf("more than 1 servers in group, ignoring %v", pg)
-		return nil
-	}
-
-	if auto, exists := pg.Labels[d.labelName(labelAutoCreated)]; exists && auto == "true" {
-		_, err := d.getClient().PlacementGroup.Delete(context.Background(), pg)
-		if err != nil {
-			return fmt.Errorf("could not remove placement group: %w", err)
-		}
-		return nil
-	} else {
-		log.Debugf("group not auto-created, ignoring: %v", pg)
-		return nil
-	}
-}
-
-func (d *Driver) getPrimaryIPv4() (*hcloud.PrimaryIP, error) {
-	raw := d.PrimaryIPv4
-	if raw == "" {
-		return nil, nil
-	} else if d.cachedPrimaryIPv4 != nil {
-		return d.cachedPrimaryIPv4, nil
-	}
-
-	ip, err := d.resolvePrimaryIP(raw)
-	d.cachedPrimaryIPv4 = ip
-	return ip, err
-}
-
-func (d *Driver) getPrimaryIPv6() (*hcloud.PrimaryIP, error) {
-	raw := d.PrimaryIPv6
-	if raw == "" {
-		return nil, nil
-	} else if d.cachedPrimaryIPv6 != nil {
-		return d.cachedPrimaryIPv6, nil
-	}
-
-	ip, err := d.resolvePrimaryIP(raw)
-	d.cachedPrimaryIPv6 = ip
-	return ip, err
-}
-
-func (d *Driver) resolvePrimaryIP(raw string) (*hcloud.PrimaryIP, error) {
-	client := d.getClient().PrimaryIP
-
-	var getter func(context.Context, string) (*hcloud.PrimaryIP, *hcloud.Response, error)
-	if net.ParseIP(raw) != nil {
-		getter = client.GetByIP
-	} else {
-		getter = client.Get
-	}
-
-	ip, _, err := getter(context.Background(), raw)
-
-	if err != nil {
-		return nil, fmt.Errorf("could not get primary IP: %w", err)
-	}
-
-	if ip != nil {
-		return instrumented(ip), nil
-	}
-
-	return nil, fmt.Errorf("primary IP not found: %v", raw)
-}
diff --git a/driver/cleanup.go b/driver/cleanup.go
new file mode 100644
index 0000000000000000000000000000000000000000..1f1ac48da171c9f5d262c7740f9d8b5950b7b8e0
--- /dev/null
+++ b/driver/cleanup.go
@@ -0,0 +1,72 @@
+package driver
+
+import (
+	"context"
+	"fmt"
+	"github.com/docker/machine/libmachine/log"
+	"github.com/hetznercloud/hcloud-go/hcloud"
+	"github.com/pkg/errors"
+)
+
+func (d *Driver) destroyDangling() {
+	for _, destructor := range d.dangling {
+		destructor()
+	}
+}
+
+func (d *Driver) removeEmptyServerPlacementGroup(srv *hcloud.Server) error {
+	pg := srv.PlacementGroup
+	if pg == nil {
+		return nil
+	}
+
+	if len(pg.Servers) > 1 {
+		log.Debugf("more than 1 servers in group, ignoring %v", pg)
+		return nil
+	}
+
+	if auto, exists := pg.Labels[d.labelName(labelAutoCreated)]; exists && auto == "true" {
+		_, err := d.getClient().PlacementGroup.Delete(context.Background(), pg)
+		if err != nil {
+			return fmt.Errorf("could not remove placement group: %w", err)
+		}
+		return nil
+	} else {
+		log.Debugf("group not auto-created, ignoring: %v", pg)
+		return nil
+	}
+}
+
+func (d *Driver) destroyServer() error {
+	if d.ServerID == 0 {
+		return nil
+	}
+
+	srv, err := d.getServerHandle()
+	if err != nil {
+		return errors.Wrap(err, "could not get server handle")
+	}
+
+	if srv == nil {
+		log.Infof(" -> Server does not exist anymore")
+	} else {
+		log.Infof(" -> Destroying server %s[%d] in...", srv.Name, srv.ID)
+
+		res, _, err := d.getClient().Server.DeleteWithResult(context.Background(), srv)
+		if err != nil {
+			return errors.Wrap(err, "could not delete server")
+		}
+
+		// failure to remove a placement group is not a hard error
+		if softErr := d.removeEmptyServerPlacementGroup(srv); softErr != nil {
+			log.Error(softErr)
+		}
+
+		// wait for the server to actually be deleted
+		if err = d.waitForAction(res.Action); err != nil {
+			return errors.Wrap(err, "could not wait for deletion")
+		}
+	}
+
+	return nil
+}
diff --git a/driver/driver.go b/driver/driver.go
new file mode 100644
index 0000000000000000000000000000000000000000..d9cb69cfb0bf66b9149c99ff49d1286e3a6507dd
--- /dev/null
+++ b/driver/driver.go
@@ -0,0 +1,635 @@
+package driver
+
+import (
+	"context"
+	"fmt"
+	"net"
+	"time"
+
+	"github.com/docker/machine/libmachine/drivers"
+	"github.com/docker/machine/libmachine/log"
+	"github.com/docker/machine/libmachine/mcnflag"
+	"github.com/docker/machine/libmachine/state"
+	"github.com/hetznercloud/hcloud-go/hcloud"
+	"github.com/pkg/errors"
+)
+
+// Driver contains hetzner-specific data to implement [drivers.Driver]
+type Driver struct {
+	*drivers.BaseDriver
+
+	AccessToken       string
+	Image             string
+	ImageID           int
+	ImageArch         hcloud.Architecture
+	cachedImage       *hcloud.Image
+	Type              string
+	cachedType        *hcloud.ServerType
+	Location          string
+	cachedLocation    *hcloud.Location
+	KeyID             int
+	cachedKey         *hcloud.SSHKey
+	IsExistingKey     bool
+	originalKey       string
+	dangling          []func()
+	ServerID          int
+	cachedServer      *hcloud.Server
+	userData          string
+	userDataFile      string
+	Volumes           []string
+	Networks          []string
+	UsePrivateNetwork bool
+	DisablePublic4    bool
+	DisablePublic6    bool
+	PrimaryIPv4       string
+	cachedPrimaryIPv4 *hcloud.PrimaryIP
+	PrimaryIPv6       string
+	cachedPrimaryIPv6 *hcloud.PrimaryIP
+	Firewalls         []string
+	ServerLabels      map[string]string
+	keyLabels         map[string]string
+	placementGroup    string
+	cachedPGrp        *hcloud.PlacementGroup
+
+	AdditionalKeys       []string
+	AdditionalKeyIDs     []int
+	cachedAdditionalKeys []*hcloud.SSHKey
+
+	WaitOnError int
+
+	// internal housekeeping
+	version string
+}
+
+const (
+	defaultImage = "ubuntu-18.04"
+	defaultType  = "cx11"
+
+	flagAPIToken          = "hetzner-api-token"
+	flagImage             = "hetzner-image"
+	flagImageID           = "hetzner-image-id"
+	flagImageArch         = "hetzner-image-arch"
+	flagType              = "hetzner-server-type"
+	flagLocation          = "hetzner-server-location"
+	flagExKeyID           = "hetzner-existing-key-id"
+	flagExKeyPath         = "hetzner-existing-key-path"
+	flagUserData          = "hetzner-user-data"
+	flagUserDataFile      = "hetzner-user-data-file"
+	flagVolumes           = "hetzner-volumes"
+	flagNetworks          = "hetzner-networks"
+	flagUsePrivateNetwork = "hetzner-use-private-network"
+	flagDisablePublic4    = "hetzner-disable-public-ipv4"
+	flagDisablePublic6    = "hetzner-disable-public-ipv6"
+	flagPrimary4          = "hetzner-primary-ipv4"
+	flagPrimary6          = "hetzner-primary-ipv6"
+	flagDisablePublic     = "hetzner-disable-public"
+	flagFirewalls         = "hetzner-firewalls"
+	flagAdditionalKeys    = "hetzner-additional-key"
+	flagServerLabel       = "hetzner-server-label"
+	flagKeyLabel          = "hetzner-key-label"
+	flagPlacementGroup    = "hetzner-placement-group"
+	flagAutoSpread        = "hetzner-auto-spread"
+
+	flagSshUser = "hetzner-ssh-user"
+	flagSshPort = "hetzner-ssh-port"
+
+	defaultSSHPort = 22
+	defaultSSHUser = "root"
+
+	flagWaitOnError    = "hetzner-wait-on-error"
+	defaultWaitOnError = 0
+
+	legacyFlagUserDataFromFile = "hetzner-user-data-from-file"
+	legacyFlagDisablePublic4   = "hetzner-disable-public-4"
+	legacyFlagDisablePublic6   = "hetzner-disable-public-6"
+
+	emptyImageArchitecture = hcloud.Architecture("")
+)
+
+// NewDriver initializes a new driver instance; see [drivers.Driver.NewDriver]
+func NewDriver(version string) *Driver {
+	if runningInstrumented {
+		instrumented("running instrument mode") // will be a no-op when not built with instrumentation
+	}
+	return &Driver{
+		Type:          defaultType,
+		IsExistingKey: false,
+		BaseDriver:    &drivers.BaseDriver{},
+		version:       version,
+	}
+}
+
+// DriverName returns the hard-coded string "hetzner"; see [drivers.Driver.DriverName]
+func (d *Driver) DriverName() string {
+	return "hetzner"
+}
+
+// GetCreateFlags retrieves additional driver-specific arguments; see [drivers.Driver.GetCreateFlags]
+func (d *Driver) GetCreateFlags() []mcnflag.Flag {
+	return []mcnflag.Flag{
+		mcnflag.StringFlag{
+			EnvVar: "HETZNER_API_TOKEN",
+			Name:   flagAPIToken,
+			Usage:  "Project-specific Hetzner API token",
+			Value:  "",
+		},
+		mcnflag.StringFlag{
+			EnvVar: "HETZNER_IMAGE",
+			Name:   flagImage,
+			Usage:  "Image to use for server creation",
+			Value:  "",
+		},
+		mcnflag.IntFlag{
+			EnvVar: "HETZNER_IMAGE_ID",
+			Name:   flagImageID,
+			Usage:  "Image to use for server creation",
+		},
+		mcnflag.StringFlag{
+			EnvVar: "HETZNER_IMAGE_ARCH",
+			Name:   flagImageArch,
+			Usage:  "Image architecture for lookup to use for server creation",
+		},
+		mcnflag.StringFlag{
+			EnvVar: "HETZNER_TYPE",
+			Name:   flagType,
+			Usage:  "Server type to create",
+			Value:  defaultType,
+		},
+		mcnflag.StringFlag{
+			EnvVar: "HETZNER_LOCATION",
+			Name:   flagLocation,
+			Usage:  "Location to create machine at",
+			Value:  "",
+		},
+		mcnflag.IntFlag{
+			EnvVar: "HETZNER_EXISTING_KEY_ID",
+			Name:   flagExKeyID,
+			Usage:  "Existing key ID to use for server; requires --hetzner-existing-key-path",
+			Value:  0,
+		},
+		mcnflag.StringFlag{
+			EnvVar: "HETZNER_EXISTING_KEY_PATH",
+			Name:   flagExKeyPath,
+			Usage:  "Path to existing key (new public key will be created unless --hetzner-existing-key-id is specified)",
+			Value:  "",
+		},
+		mcnflag.StringFlag{
+			EnvVar: "HETZNER_USER_DATA",
+			Name:   flagUserData,
+			Usage:  "Cloud-init based user data (inline).",
+			Value:  "",
+		},
+		mcnflag.BoolFlag{
+			EnvVar: "HETZNER_USER_DATA_FROM_FILE",
+			Name:   legacyFlagUserDataFromFile,
+			Usage:  "DEPRECATED, use --hetzner-user-data-file. Treat --hetzner-user-data argument as filename.",
+		},
+		mcnflag.StringFlag{
+			EnvVar: "HETZNER_USER_DATA_FILE",
+			Name:   flagUserDataFile,
+			Usage:  "Cloud-init based user data (read from file)",
+			Value:  "",
+		},
+		mcnflag.StringSliceFlag{
+			EnvVar: "HETZNER_VOLUMES",
+			Name:   flagVolumes,
+			Usage:  "Volume IDs or names which should be attached to the server",
+			Value:  []string{},
+		},
+		mcnflag.StringSliceFlag{
+			EnvVar: "HETZNER_NETWORKS",
+			Name:   flagNetworks,
+			Usage:  "Network IDs or names which should be attached to the server private network interface",
+			Value:  []string{},
+		},
+		mcnflag.BoolFlag{
+			EnvVar: "HETZNER_USE_PRIVATE_NETWORK",
+			Name:   flagUsePrivateNetwork,
+			Usage:  "Use private network",
+		},
+		mcnflag.BoolFlag{
+			EnvVar: "HETZNER_DISABLE_PUBLIC_IPV4",
+			Name:   flagDisablePublic4,
+			Usage:  "Disable public ipv4",
+		},
+		mcnflag.BoolFlag{
+			EnvVar: "HETZNER_DISABLE_PUBLIC_4",
+			Name:   legacyFlagDisablePublic4,
+			Usage:  "DEPRECATED, use --hetzner-disable-public-ipv4; disable public ipv4",
+		},
+		mcnflag.BoolFlag{
+			EnvVar: "HETZNER_DISABLE_PUBLIC_IPV6",
+			Name:   flagDisablePublic6,
+			Usage:  "Disable public ipv6",
+		},
+		mcnflag.BoolFlag{
+			EnvVar: "HETZNER_DISABLE_PUBLIC_6",
+			Name:   legacyFlagDisablePublic6,
+			Usage:  "DEPRECATED, use --hetzner-disable-public-ipv6; disable public ipv6",
+		},
+		mcnflag.BoolFlag{
+			EnvVar: "HETZNER_DISABLE_PUBLIC",
+			Name:   flagDisablePublic,
+			Usage:  "Disable public ip (v4 & v6)",
+		},
+		mcnflag.StringFlag{
+			EnvVar: "HETZNER_PRIMARY_IPV4",
+			Name:   flagPrimary4,
+			Usage:  "Existing primary IPv4 address",
+			Value:  "",
+		},
+		mcnflag.StringFlag{
+			EnvVar: "HETZNER_PRIMARY_IPV6",
+			Name:   flagPrimary6,
+			Usage:  "Existing primary IPv6 address",
+			Value:  "",
+		},
+		mcnflag.StringSliceFlag{
+			EnvVar: "HETZNER_FIREWALLS",
+			Name:   flagFirewalls,
+			Usage:  "Firewall IDs or names which should be applied on the server",
+			Value:  []string{},
+		},
+		mcnflag.StringSliceFlag{
+			EnvVar: "HETZNER_ADDITIONAL_KEYS",
+			Name:   flagAdditionalKeys,
+			Usage:  "Additional public keys to be attached to the server",
+			Value:  []string{},
+		},
+		mcnflag.StringSliceFlag{
+			EnvVar: "HETZNER_SERVER_LABELS",
+			Name:   flagServerLabel,
+			Usage:  "Key value pairs of additional labels to assign to the server",
+			Value:  []string{},
+		},
+		mcnflag.StringSliceFlag{
+			EnvVar: "HETZNER_KEY_LABELS",
+			Name:   flagKeyLabel,
+			Usage:  "Key value pairs of additional labels to assign to the SSH key",
+			Value:  []string{},
+		},
+		mcnflag.StringFlag{
+			EnvVar: "HETZNER_PLACEMENT_GROUP",
+			Name:   flagPlacementGroup,
+			Usage:  "Placement group ID or name to add the server to; will be created if it does not exist",
+			Value:  "",
+		},
+		mcnflag.BoolFlag{
+			EnvVar: "HETZNER_AUTO_SPREAD",
+			Name:   flagAutoSpread,
+			Usage:  "Auto-spread on a docker-machine-specific default placement group",
+		},
+		mcnflag.StringFlag{
+			EnvVar: "HETZNER_SSH_USER",
+			Name:   flagSshUser,
+			Usage:  "SSH username",
+			Value:  defaultSSHUser,
+		},
+		mcnflag.IntFlag{
+			EnvVar: "HETZNER_SSH_PORT",
+			Name:   flagSshPort,
+			Usage:  "SSH port",
+			Value:  defaultSSHPort,
+		},
+		mcnflag.IntFlag{
+			EnvVar: "HETZNER_WAIT_ON_ERROR",
+			Name:   flagWaitOnError,
+			Usage:  "Wait if an error happens while creating the server",
+			Value:  defaultWaitOnError,
+		},
+	}
+}
+
+// SetConfigFromFlags handles additional driver arguments as retrieved by [Driver.GetCreateFlags];
+// see [drivers.Driver.SetConfigFromFlags]
+func (d *Driver) SetConfigFromFlags(opts drivers.DriverOptions) error {
+	return d.setConfigFromFlags(opts)
+}
+
+func (d *Driver) setConfigFromFlagsImpl(opts drivers.DriverOptions) error {
+	d.AccessToken = opts.String(flagAPIToken)
+	d.Image = opts.String(flagImage)
+	d.ImageID = opts.Int(flagImageID)
+	err := d.setImageArch(opts.String(flagImageArch))
+	if err != nil {
+		return err
+	}
+	d.Location = opts.String(flagLocation)
+	d.Type = opts.String(flagType)
+	d.KeyID = opts.Int(flagExKeyID)
+	d.IsExistingKey = d.KeyID != 0
+	d.originalKey = opts.String(flagExKeyPath)
+	err = d.setUserDataFlags(opts)
+	if err != nil {
+		return err
+	}
+	d.Volumes = opts.StringSlice(flagVolumes)
+	d.Networks = opts.StringSlice(flagNetworks)
+	disablePublic := opts.Bool(flagDisablePublic)
+	d.UsePrivateNetwork = opts.Bool(flagUsePrivateNetwork) || disablePublic
+	d.DisablePublic4 = d.deprecatedBooleanFlag(opts, flagDisablePublic4, legacyFlagDisablePublic4) || disablePublic
+	d.DisablePublic6 = d.deprecatedBooleanFlag(opts, flagDisablePublic6, legacyFlagDisablePublic6) || disablePublic
+	d.PrimaryIPv4 = opts.String(flagPrimary4)
+	d.PrimaryIPv6 = opts.String(flagPrimary6)
+	d.Firewalls = opts.StringSlice(flagFirewalls)
+	d.AdditionalKeys = opts.StringSlice(flagAdditionalKeys)
+
+	d.SSHUser = opts.String(flagSshUser)
+	d.SSHPort = opts.Int(flagSshPort)
+
+	d.WaitOnError = opts.Int(flagWaitOnError)
+
+	d.placementGroup = opts.String(flagPlacementGroup)
+	if opts.Bool(flagAutoSpread) {
+		if d.placementGroup != "" {
+			return d.flagFailure("%v and %v are mutually exclusive", flagAutoSpread, flagPlacementGroup)
+		}
+		d.placementGroup = autoSpreadPgName
+	}
+
+	err = d.setLabelsFromFlags(opts)
+	if err != nil {
+		return err
+	}
+
+	d.SetSwarmConfigFromFlags(opts)
+
+	if d.AccessToken == "" {
+		return d.flagFailure("hetzner requires --%v to be set", flagAPIToken)
+	}
+
+	if err = d.verifyImageFlags(); err != nil {
+		return err
+	}
+
+	if err = d.verifyNetworkFlags(); err != nil {
+		return err
+	}
+
+	instrumented(d)
+
+	return nil
+}
+
+// GetSSHUsername retrieves the SSH username used to connect to the server during provisioning
+func (d *Driver) GetSSHUsername() string {
+	return d.SSHUser
+}
+
+// GetSSHPort retrieves the port used to connect to the server during provisioning
+func (d *Driver) GetSSHPort() (int, error) {
+	return d.SSHPort, nil
+}
+
+// PreCreateCheck validates the Driver data is in a valid state for creation; see [drivers.Driver.PreCreateCheck]
+func (d *Driver) PreCreateCheck() error {
+	if err := d.setupExistingKey(); err != nil {
+		return err
+	}
+
+	if serverType, err := d.getType(); err != nil {
+		return errors.Wrap(err, "could not get type")
+	} else if d.ImageArch != "" && serverType.Architecture != d.ImageArch {
+		log.Warnf("supplied architecture %v differs from server architecture %v", d.ImageArch, serverType.Architecture)
+	}
+
+	if _, err := d.getImage(); err != nil {
+		return errors.Wrap(err, "could not get image")
+	}
+
+	if _, err := d.getLocation(); err != nil {
+		return errors.Wrap(err, "could not get location")
+	}
+
+	if _, err := d.getPlacementGroup(); err != nil {
+		return fmt.Errorf("could not create placement group: %w", err)
+	}
+
+	if _, err := d.getPrimaryIPv4(); err != nil {
+		return fmt.Errorf("could not resolve primary IPv4: %w", err)
+	}
+
+	if _, err := d.getPrimaryIPv6(); err != nil {
+		return fmt.Errorf("could not resolve primary IPv6: %w", err)
+	}
+
+	if d.UsePrivateNetwork && len(d.Networks) == 0 {
+		return errors.Errorf("No private network attached.")
+	}
+
+	return nil
+}
+
+// Create actually creates the hetzner-cloud server; see [drivers.Driver.Create]
+func (d *Driver) Create() error {
+	err := d.prepareLocalKey()
+	if err != nil {
+		return err
+	}
+
+	defer d.destroyDangling()
+	err = d.createRemoteKeys()
+	if err != nil {
+		return err
+	}
+
+	log.Infof("Creating Hetzner server...")
+
+	srvopts, err := d.makeCreateServerOptions()
+	if err != nil {
+		return err
+	}
+
+	srv, _, err := d.getClient().Server.Create(context.Background(), instrumented(*srvopts))
+	if err != nil {
+		time.Sleep(time.Duration(d.WaitOnError) * time.Second)
+		return errors.Wrap(err, "could not create server")
+	}
+
+	log.Infof(" -> Creating server %s[%d] in %s[%d]", srv.Server.Name, srv.Server.ID, srv.Action.Command, srv.Action.ID)
+	if err = d.waitForAction(srv.Action); err != nil {
+		return errors.Wrap(err, "could not wait for action")
+	}
+
+	d.ServerID = srv.Server.ID
+	log.Infof(" -> Server %s[%d]: Waiting to come up...", srv.Server.Name, srv.Server.ID)
+
+	err = d.waitForRunningServer()
+	if err != nil {
+		return err
+	}
+
+	err = d.configureNetworkAccess(srv)
+	if err != nil {
+		return err
+	}
+
+	log.Infof(" -> Server %s[%d] ready. Ip %s", srv.Server.Name, srv.Server.ID, d.IPAddress)
+	// Successful creation, so no keys dangle anymore
+	d.dangling = nil
+
+	return nil
+}
+
+// GetSSHHostname retrieves the SSH host to connect to the machine; see [drivers.Driver.GetSSHHostname]
+func (d *Driver) GetSSHHostname() (string, error) {
+	return d.GetIP()
+}
+
+// GetURL retrieves the URL of the docker daemon on the machine; see [drivers.Driver.GetURL]
+func (d *Driver) GetURL() (string, error) {
+	if err := drivers.MustBeRunning(d); err != nil {
+		return "", errors.Wrap(err, "could not execute drivers.MustBeRunning")
+	}
+
+	ip, err := d.GetIP()
+	if err != nil {
+		return "", errors.Wrap(err, "could not get IP")
+	}
+
+	return fmt.Sprintf("tcp://%s", net.JoinHostPort(ip, "2376")), nil
+}
+
+// GetState retrieves the state the machine is currently in; see [drivers.Driver.GetState]
+func (d *Driver) GetState() (state.State, error) {
+	srv, _, err := d.getClient().Server.GetByID(context.Background(), d.ServerID)
+	if err != nil {
+		return state.None, errors.Wrap(err, "could not get server by ID")
+	}
+	if srv == nil {
+		return state.None, errors.New("server not found")
+	}
+
+	switch srv.Status {
+	case hcloud.ServerStatusInitializing:
+		return state.Starting, nil
+	case hcloud.ServerStatusRunning:
+		return state.Running, nil
+	case hcloud.ServerStatusOff:
+		return state.Stopped, nil
+	}
+	return state.None, nil
+}
+
+// Remove deletes the hetzner server and additional resources created during creation; see [drivers.Driver.Remove]
+func (d *Driver) Remove() error {
+	if err := d.destroyServer(); err != nil {
+		return err
+	}
+
+	// failure to remove a key is not ha hard error
+	for i, id := range d.AdditionalKeyIDs {
+		log.Infof(" -> Destroying additional key #%d (%d)", i, id)
+		key, _, softErr := d.getClient().SSHKey.GetByID(context.Background(), id)
+		if softErr != nil {
+			log.Warnf(" ->  -> could not retrieve key %v", softErr)
+		} else if key == nil {
+			log.Warnf(" ->  -> %d no longer exists", id)
+		}
+
+		_, softErr = d.getClient().SSHKey.Delete(context.Background(), key)
+		if softErr != nil {
+			log.Warnf(" ->  -> could not remove key: %v", softErr)
+		}
+	}
+
+	// failure to remove a server-specific key is a hard error
+	if !d.IsExistingKey && d.KeyID != 0 {
+		key, err := d.getKey()
+		if err != nil {
+			return errors.Wrap(err, "could not get ssh key")
+		}
+		if key == nil {
+			log.Infof(" -> SSH key does not exist anymore")
+			return nil
+		}
+
+		log.Infof(" -> Destroying SSHKey %s[%d]...", key.Name, key.ID)
+
+		if _, err := d.getClient().SSHKey.Delete(context.Background(), key); err != nil {
+			return errors.Wrap(err, "could not delete ssh key")
+		}
+	}
+
+	return nil
+}
+
+// Restart instructs the hetzner cloud server to reboot; see [drivers.Driver.Restart]
+func (d *Driver) Restart() error {
+	srv, err := d.getServerHandle()
+	if err != nil {
+		return errors.Wrap(err, "could not get server handle")
+	}
+	if srv == nil {
+		return errors.New("server not found")
+	}
+
+	act, _, err := d.getClient().Server.Reboot(context.Background(), srv)
+	if err != nil {
+		return errors.Wrap(err, "could not reboot server")
+	}
+
+	log.Infof(" -> Rebooting server %s[%d] in %s[%d]...", srv.Name, srv.ID, act.Command, act.ID)
+
+	return d.waitForAction(act)
+}
+
+// Start instructs the hetzner cloud server to power up; see [drivers.Driver.Start]
+func (d *Driver) Start() error {
+	srv, err := d.getServerHandle()
+	if err != nil {
+		return errors.Wrap(err, "could not get server handle")
+	}
+	if srv == nil {
+		return errors.New("server not found")
+	}
+
+	act, _, err := d.getClient().Server.Poweron(context.Background(), srv)
+	if err != nil {
+		return errors.Wrap(err, "could not power on server")
+	}
+
+	log.Infof(" -> Starting server %s[%d] in %s[%d]...", srv.Name, srv.ID, act.Command, act.ID)
+
+	return d.waitForAction(act)
+}
+
+// Stop instructs the hetzner cloud server to shut down; see [drivers.Driver.Stop]
+func (d *Driver) Stop() error {
+	srv, err := d.getServerHandle()
+	if err != nil {
+		return errors.Wrap(err, "could not get server handle")
+	}
+	if srv == nil {
+		return errors.New("server not found")
+	}
+
+	act, _, err := d.getClient().Server.Shutdown(context.Background(), srv)
+	if err != nil {
+		return errors.Wrap(err, "could not shutdown server")
+	}
+
+	log.Infof(" -> Shutting down server %s[%d] in %s[%d]...", srv.Name, srv.ID, act.Command, act.ID)
+
+	return d.waitForAction(act)
+}
+
+// Kill forcefully shuts down the hetzner cloud server; see [drivers.Driver.Kill]
+func (d *Driver) Kill() error {
+	srv, err := d.getServerHandle()
+	if err != nil {
+		return errors.Wrap(err, "could not get server handle")
+	}
+	if srv == nil {
+		return errors.New("server not found")
+	}
+
+	act, _, err := d.getClient().Server.Poweroff(context.Background(), srv)
+	if err != nil {
+		return errors.Wrap(err, "could not poweroff server")
+	}
+
+	log.Infof(" -> Powering off server %s[%d] in %s[%d]...", srv.Name, srv.ID, act.Command, act.ID)
+
+	return d.waitForAction(act)
+}
diff --git a/driver_test.go b/driver/driver_test.go
similarity index 94%
rename from driver_test.go
rename to driver/driver_test.go
index 5f9ea489dca81d3dfd0a394f1142748113729dc7..fd2faca94b16c18c8f21c880e5254883b35cca51 100644
--- a/driver_test.go
+++ b/driver/driver_test.go
@@ -1,4 +1,4 @@
-package main
+package driver
 
 import (
 	"github.com/docker/machine/commands/commandstest"
@@ -36,7 +36,7 @@ func TestUserData(t *testing.T) {
 	}
 
 	// mutual exclusion data <=> data file
-	d := NewDriver()
+	d := NewDriver("test")
 	err = d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{
 		flagUserData:     inlineContents,
 		flagUserDataFile: file,
@@ -44,7 +44,7 @@ func TestUserData(t *testing.T) {
 	assertMutualExclusion(t, err, flagUserData, flagUserDataFile)
 
 	// mutual exclusion data file <=> legacy flag
-	d = NewDriver()
+	d = NewDriver("test")
 	err = d.setConfigFromFlagsImpl(&commandstest.FakeFlagger{
 		Data: map[string]interface{}{
 			legacyFlagUserDataFromFile: true,
@@ -54,7 +54,7 @@ func TestUserData(t *testing.T) {
 	assertMutualExclusion(t, err, legacyFlagUserDataFromFile, flagUserDataFile)
 
 	// inline user data
-	d = NewDriver()
+	d = NewDriver("test")
 	err = d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{
 		flagUserData: inlineContents,
 	}))
@@ -71,7 +71,7 @@ func TestUserData(t *testing.T) {
 	}
 
 	// file user data
-	d = NewDriver()
+	d = NewDriver("test")
 	err = d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{
 		flagUserDataFile: file,
 	}))
@@ -88,7 +88,7 @@ func TestUserData(t *testing.T) {
 	}
 
 	// legacy file user data
-	d = NewDriver()
+	d = NewDriver("test")
 	err = d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{
 		flagUserData:               file,
 		legacyFlagUserDataFromFile: true,
@@ -107,7 +107,7 @@ func TestUserData(t *testing.T) {
 }
 
 func TestDisablePublic(t *testing.T) {
-	d := NewDriver()
+	d := NewDriver("test")
 	err := d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{
 		flagDisablePublic: true,
 	}))
@@ -127,7 +127,7 @@ func TestDisablePublic(t *testing.T) {
 }
 
 func TestDisablePublic46(t *testing.T) {
-	d := NewDriver()
+	d := NewDriver("test")
 	err := d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{
 		flagDisablePublic4: true,
 	}))
@@ -146,7 +146,7 @@ func TestDisablePublic46(t *testing.T) {
 	}
 
 	// 6
-	d = NewDriver()
+	d = NewDriver("test")
 	err = d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{
 		flagDisablePublic6: true,
 	}))
@@ -166,7 +166,7 @@ func TestDisablePublic46(t *testing.T) {
 }
 
 func TestDisablePublic46Legacy(t *testing.T) {
-	d := NewDriver()
+	d := NewDriver("test")
 	err := d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{
 		legacyFlagDisablePublic4: true,
 		// any truthy flag should take precedence
@@ -187,7 +187,7 @@ func TestDisablePublic46Legacy(t *testing.T) {
 	}
 
 	// 6
-	d = NewDriver()
+	d = NewDriver("test")
 	err = d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{
 		legacyFlagDisablePublic6: true,
 		// any truthy flag should take precedence
@@ -210,7 +210,7 @@ func TestDisablePublic46Legacy(t *testing.T) {
 
 func TestImageFlagExclusions(t *testing.T) {
 	// both id and name given
-	d := NewDriver()
+	d := NewDriver("test")
 	err := d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{
 		flagImageID: 42,
 		flagImage:   "answer",
@@ -218,7 +218,7 @@ func TestImageFlagExclusions(t *testing.T) {
 	assertMutualExclusion(t, err, flagImageID, flagImage)
 
 	// both id and arch given
-	d = NewDriver()
+	d = NewDriver("test")
 	err = d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{
 		flagImageID:   42,
 		flagImageArch: string(hcloud.ArchitectureX86),
@@ -228,7 +228,7 @@ func TestImageFlagExclusions(t *testing.T) {
 
 func TestImageArch(t *testing.T) {
 	// no explicit arch
-	d := NewDriver()
+	d := NewDriver("test")
 	err := d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{
 		flagImage: "answer",
 	}))
@@ -245,7 +245,7 @@ func TestImageArch(t *testing.T) {
 	testArchFlag(t, hcloud.ArchitectureX86)
 
 	// invalid
-	d = NewDriver()
+	d = NewDriver("test")
 	err = d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{
 		flagImage:     "answer",
 		flagImageArch: "hal9000",
@@ -256,7 +256,7 @@ func TestImageArch(t *testing.T) {
 }
 
 func testArchFlag(t *testing.T, arch hcloud.Architecture) {
-	d := NewDriver()
+	d := NewDriver("test")
 	err := d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{
 		flagImage:     "answer",
 		flagImageArch: string(arch),
diff --git a/flag_failure.go b/driver/flag_failure.go
similarity index 95%
rename from flag_failure.go
rename to driver/flag_failure.go
index 262547ed495b10b36ed3a80cec3b6978919ddd7f..f2a43f7abd03bc74d329329c0d3e8f071dbe286f 100644
--- a/flag_failure.go
+++ b/driver/flag_failure.go
@@ -1,6 +1,6 @@
 //go:build !flag_debug && !instrumented
 
-package main
+package driver
 
 import (
 	"github.com/docker/machine/libmachine/drivers"
diff --git a/flag_failure_debug.go b/driver/flag_failure_debug.go
similarity index 98%
rename from flag_failure_debug.go
rename to driver/flag_failure_debug.go
index 333c33895bd7bc30692b949f377d75e71a5c61ef..d4b533b9e45ad8c390f844076ce68746b68fd196 100644
--- a/flag_failure_debug.go
+++ b/driver/flag_failure_debug.go
@@ -1,6 +1,6 @@
 //go:build flag_debug || instrumented
 
-package main
+package driver
 
 import (
 	"encoding/json"
diff --git a/driver/flag_processing.go b/driver/flag_processing.go
new file mode 100644
index 0000000000000000000000000000000000000000..077ea1b79be3e8b837c983f5a30f43e800145c73
--- /dev/null
+++ b/driver/flag_processing.go
@@ -0,0 +1,102 @@
+package driver
+
+import (
+	"github.com/docker/machine/libmachine/drivers"
+	"github.com/docker/machine/libmachine/log"
+	"github.com/hetznercloud/hcloud-go/hcloud"
+	"github.com/pkg/errors"
+	"strings"
+)
+
+func (d *Driver) setImageArch(arch string) error {
+	switch arch {
+	case "":
+		d.ImageArch = emptyImageArchitecture
+	case string(hcloud.ArchitectureARM):
+		d.ImageArch = hcloud.ArchitectureARM
+	case string(hcloud.ArchitectureX86):
+		d.ImageArch = hcloud.ArchitectureX86
+	default:
+		return errors.Errorf("unknown architecture %v", arch)
+	}
+	return nil
+}
+
+func (d *Driver) verifyImageFlags() error {
+	if d.ImageID != 0 && d.Image != "" && d.Image != defaultImage /* support legacy behaviour */ {
+		return d.flagFailure("--%v and --%v are mutually exclusive", flagImage, flagImageID)
+	} else if d.ImageID != 0 && d.ImageArch != "" {
+		return d.flagFailure("--%v and --%v are mutually exclusive", flagImageArch, flagImageID)
+	} else if d.ImageID == 0 && d.Image == "" {
+		d.Image = defaultImage
+	}
+	return nil
+}
+
+func (d *Driver) verifyNetworkFlags() error {
+	if d.DisablePublic4 && d.DisablePublic6 && !d.UsePrivateNetwork {
+		return d.flagFailure("--%v must be used if public networking is disabled (hint: implicitly set by --%v)",
+			flagUsePrivateNetwork, flagDisablePublic)
+	}
+
+	if d.DisablePublic4 && d.PrimaryIPv4 != "" {
+		return d.flagFailure("--%v and --%v are mutually exclusive", flagPrimary4, flagDisablePublic4)
+	}
+
+	if d.DisablePublic6 && d.PrimaryIPv6 != "" {
+		return d.flagFailure("--%v and --%v are mutually exclusive", flagPrimary6, flagDisablePublic6)
+	}
+	return nil
+}
+
+func (d *Driver) deprecatedBooleanFlag(opts drivers.DriverOptions, flag, deprecatedFlag string) bool {
+	if opts.Bool(deprecatedFlag) {
+		log.Warnf("--%v is deprecated, use --%v instead", deprecatedFlag, flag)
+		return true
+	}
+	return opts.Bool(flag)
+}
+
+func (d *Driver) setUserDataFlags(opts drivers.DriverOptions) error {
+	userData := opts.String(flagUserData)
+	userDataFile := opts.String(flagUserDataFile)
+
+	if opts.Bool(legacyFlagUserDataFromFile) {
+		if userDataFile != "" {
+			return d.flagFailure("--%v and --%v are mutually exclusive", flagUserDataFile, legacyFlagUserDataFromFile)
+		}
+
+		log.Warnf("--%v is deprecated, pass '--%v \"%v\"'", legacyFlagUserDataFromFile, flagUserDataFile, userData)
+		d.userDataFile = userData
+		return nil
+	}
+
+	d.userData = userData
+	d.userDataFile = userDataFile
+
+	if d.userData != "" && d.userDataFile != "" {
+		return d.flagFailure("--%v and --%v are mutually exclusive", flagUserData, flagUserDataFile)
+	}
+
+	return nil
+}
+
+func (d *Driver) setLabelsFromFlags(opts drivers.DriverOptions) error {
+	d.ServerLabels = make(map[string]string)
+	for _, label := range opts.StringSlice(flagServerLabel) {
+		split := strings.SplitN(label, "=", 2)
+		if len(split) != 2 {
+			return d.flagFailure("server label %v is not in key=value format", label)
+		}
+		d.ServerLabels[split[0]] = split[1]
+	}
+	d.keyLabels = make(map[string]string)
+	for _, label := range opts.StringSlice(flagKeyLabel) {
+		split := strings.SplitN(label, "=", 2)
+		if len(split) != 2 {
+			return errors.Errorf("key label %v is not in key=value format", label)
+		}
+		d.keyLabels[split[0]] = split[1]
+	}
+	return nil
+}
diff --git a/driver/hetzner_query.go b/driver/hetzner_query.go
new file mode 100644
index 0000000000000000000000000000000000000000..1064574268a67ac724852a0eb2a98f4e8c1ad078
--- /dev/null
+++ b/driver/hetzner_query.go
@@ -0,0 +1,150 @@
+package driver
+
+import (
+	"context"
+	"fmt"
+	"github.com/docker/machine/libmachine/log"
+	"github.com/hetznercloud/hcloud-go/hcloud"
+	"github.com/pkg/errors"
+	"golang.org/x/crypto/ssh"
+	"time"
+)
+
+func (d *Driver) getClient() *hcloud.Client {
+	return hcloud.NewClient(hcloud.WithToken(d.AccessToken), hcloud.WithApplication("docker-machine-driver", d.version))
+}
+
+func (d *Driver) getLocation() (*hcloud.Location, error) {
+	if d.cachedLocation != nil {
+		return d.cachedLocation, nil
+	}
+
+	location, _, err := d.getClient().Location.GetByName(context.Background(), d.Location)
+	if err != nil {
+		return location, errors.Wrap(err, "could not get location by name")
+	}
+	d.cachedLocation = location
+	return location, nil
+}
+
+func (d *Driver) getType() (*hcloud.ServerType, error) {
+	if d.cachedType != nil {
+		return d.cachedType, nil
+	}
+
+	stype, _, err := d.getClient().ServerType.GetByName(context.Background(), d.Type)
+	if err != nil {
+		return stype, errors.Wrap(err, "could not get type by name")
+	}
+	d.cachedType = stype
+	return instrumented(stype), nil
+}
+
+func (d *Driver) getImage() (*hcloud.Image, error) {
+	if d.cachedImage != nil {
+		return d.cachedImage, nil
+	}
+
+	var image *hcloud.Image
+	var err error
+
+	if d.ImageID != 0 {
+		image, _, err = d.getClient().Image.GetByID(context.Background(), d.ImageID)
+		if err != nil {
+			return image, errors.Wrap(err, fmt.Sprintf("could not get image by id %v", d.ImageID))
+		}
+	} else {
+		arch, err := d.getImageArchitectureForLookup()
+		if err != nil {
+			return nil, errors.Wrap(err, "could not determine image architecture")
+		}
+
+		image, _, err = d.getClient().Image.GetByNameAndArchitecture(context.Background(), d.Image, arch)
+		if err != nil {
+			return image, errors.Wrap(err, fmt.Sprintf("could not get image by name %v", d.Image))
+		}
+	}
+
+	d.cachedImage = image
+	return instrumented(image), nil
+}
+
+func (d *Driver) getImageArchitectureForLookup() (hcloud.Architecture, error) {
+	if d.ImageArch != emptyImageArchitecture {
+		return d.ImageArch, nil
+	}
+
+	serverType, err := d.getType()
+	if err != nil {
+		return "", err
+	}
+
+	return serverType.Architecture, nil
+}
+
+func (d *Driver) getKey() (*hcloud.SSHKey, error) {
+	if d.cachedKey != nil {
+		return d.cachedKey, nil
+	}
+
+	stype, _, err := d.getClient().SSHKey.GetByID(context.Background(), d.KeyID)
+	if err != nil {
+		return stype, errors.Wrap(err, "could not get sshkey by ID")
+	}
+	d.cachedKey = stype
+	return instrumented(stype), nil
+}
+
+func (d *Driver) getRemoteKeyWithSameFingerprint(publicKeyBytes []byte) (*hcloud.SSHKey, error) {
+	publicKey, _, _, _, err := ssh.ParseAuthorizedKey(publicKeyBytes)
+	if err != nil {
+		return nil, errors.Wrap(err, "could not parse ssh public key")
+	}
+
+	fp := ssh.FingerprintLegacyMD5(publicKey)
+
+	remoteKey, _, err := d.getClient().SSHKey.GetByFingerprint(context.Background(), fp)
+	if err != nil {
+		return remoteKey, errors.Wrap(err, "could not get sshkey by fingerprint")
+	}
+	return instrumented(remoteKey), nil
+}
+
+func (d *Driver) getServerHandle() (*hcloud.Server, error) {
+	if d.cachedServer != nil {
+		return d.cachedServer, nil
+	}
+
+	if d.ServerID == 0 {
+		return nil, errors.New("server ID was 0")
+	}
+
+	srv, _, err := d.getClient().Server.GetByID(context.Background(), d.ServerID)
+	if err != nil {
+		return nil, errors.Wrap(err, "could not get client by ID")
+	}
+
+	d.cachedServer = srv
+	return srv, nil
+}
+
+func (d *Driver) waitForAction(a *hcloud.Action) error {
+	for {
+		act, _, err := d.getClient().Action.GetByID(context.Background(), a.ID)
+		if err != nil {
+			return errors.Wrap(err, "could not get client by ID")
+		}
+
+		if act.Status == hcloud.ActionStatusSuccess {
+			log.Debugf(" -> finished %s[%d]", act.Command, act.ID)
+			break
+		} else if act.Status == hcloud.ActionStatusRunning {
+			log.Debugf(" -> %s[%d]: %d %%", act.Command, act.ID, act.Progress)
+		} else if act.Status == hcloud.ActionStatusError {
+			return act.Error()
+		}
+
+		time.Sleep(1 * time.Second)
+	}
+	return nil
+}
diff --git a/instrumentation_impl.go b/driver/instrumentation_impl.go
similarity index 86%
rename from instrumentation_impl.go
rename to driver/instrumentation_impl.go
index 1b7a057b450042a01e2207cb439dd0c0e4dafff8..c5d5d5b82d4eb8377066ae0fd733c0a4ceb3fa77 100644
--- a/instrumentation_impl.go
+++ b/driver/instrumentation_impl.go
@@ -1,6 +1,6 @@
 //go:build instrumented
 
-package main
+package driver
 
 import (
 	"encoding/json"
@@ -9,6 +9,8 @@ import (
 	"github.com/docker/machine/libmachine/log"
 )
 
+const runningInstrumented = false
+
 func instrumented[T any](input T) T {
 	j, err := json.Marshal(input)
 	if err != nil {
diff --git a/instrumentation_stub.go b/driver/instrumentation_stub.go
similarity index 61%
rename from instrumentation_stub.go
rename to driver/instrumentation_stub.go
index d0bbb138c37aec8a453ab3edb55aa6c6d65df328..c4e3029fc28091f1da8e10716abad4681b147840 100644
--- a/instrumentation_stub.go
+++ b/driver/instrumentation_stub.go
@@ -1,6 +1,8 @@
 //go:build !instrumented
 
-package main
+package driver
+
+const runningInstrumented = false
 
 func instrumented[T any](input T) T {
 	return input
diff --git a/driver/labels.go b/driver/labels.go
new file mode 100644
index 0000000000000000000000000000000000000000..13042da24921801d835d31c71a187f7f2c1c192d
--- /dev/null
+++ b/driver/labels.go
@@ -0,0 +1,7 @@
+package driver
+
+const labelNamespace = "docker-machine"
+
+func (d *Driver) labelName(name string) string {
+	return labelNamespace + "/" + name
+}
diff --git a/driver/networking.go b/driver/networking.go
new file mode 100644
index 0000000000000000000000000000000000000000..41777b8716f77df261ba6188b911255d512ac051
--- /dev/null
+++ b/driver/networking.go
@@ -0,0 +1,115 @@
+package driver
+
+import (
+	"context"
+	"fmt"
+	"github.com/docker/machine/libmachine/log"
+	"github.com/hetznercloud/hcloud-go/hcloud"
+	"github.com/pkg/errors"
+	"net"
+	"time"
+)
+
+func (d *Driver) getPrimaryIPv4() (*hcloud.PrimaryIP, error) {
+	raw := d.PrimaryIPv4
+	if raw == "" {
+		return nil, nil
+	} else if d.cachedPrimaryIPv4 != nil {
+		return d.cachedPrimaryIPv4, nil
+	}
+
+	ip, err := d.resolvePrimaryIP(raw)
+	d.cachedPrimaryIPv4 = ip
+	return ip, err
+}
+
+func (d *Driver) getPrimaryIPv6() (*hcloud.PrimaryIP, error) {
+	raw := d.PrimaryIPv6
+	if raw == "" {
+		return nil, nil
+	} else if d.cachedPrimaryIPv6 != nil {
+		return d.cachedPrimaryIPv6, nil
+	}
+
+	ip, err := d.resolvePrimaryIP(raw)
+	d.cachedPrimaryIPv6 = ip
+	return ip, err
+}
+
+func (d *Driver) resolvePrimaryIP(raw string) (*hcloud.PrimaryIP, error) {
+	client := d.getClient().PrimaryIP
+
+	var getter func(context.Context, string) (*hcloud.PrimaryIP, *hcloud.Response, error)
+	if net.ParseIP(raw) != nil {
+		getter = client.GetByIP
+	} else {
+		getter = client.Get
+	}
+
+	ip, _, err := getter(context.Background(), raw)
+
+	if err != nil {
+		return nil, fmt.Errorf("could not get primary IP: %w", err)
+	}
+
+	if ip != nil {
+		return instrumented(ip), nil
+	}
+
+	return nil, fmt.Errorf("primary IP not found: %v", raw)
+}
+
+func (d *Driver) setPublicNetIfRequired(srvopts *hcloud.ServerCreateOpts) error {
+	pip4, err := d.getPrimaryIPv4()
+	if err != nil {
+		return err
+	}
+	pip6, err := d.getPrimaryIPv6()
+	if err != nil {
+		return err
+	}
+
+	if d.DisablePublic4 || d.DisablePublic6 || pip4 != nil || pip6 != nil {
+		srvopts.PublicNet = &hcloud.ServerCreatePublicNet{
+			EnableIPv4: !d.DisablePublic4 || pip4 != nil,
+			EnableIPv6: !d.DisablePublic6 || pip6 != nil,
+			IPv4:       pip4,
+			IPv6:       pip6,
+		}
+	}
+	return nil
+}
+
+func (d *Driver) configureNetworkAccess(srv hcloud.ServerCreateResult) error {
+	if d.UsePrivateNetwork {
+		for {
+			// we need to wait until network is attached
+			log.Infof("Wait until private network attached ...")
+			server, _, err := d.getClient().Server.GetByID(context.Background(), srv.Server.ID)
+			if err != nil {
+				return errors.Wrapf(err, "could not get newly created server [%d]", srv.Server.ID)
+			}
+			if server.PrivateNet != nil {
+				d.IPAddress = server.PrivateNet[0].IP.String()
+				break
+			}
+			time.Sleep(1 * time.Second)
+		}
+	} else if d.DisablePublic4 {
+		log.Infof("Using public IPv6 network ...")
+
+		pv6 := srv.Server.PublicNet.IPv6
+		ip := pv6.IP
+		if ip.Mask(pv6.Network.Mask).Equal(pv6.Network.IP) { // no host given
+			ip[net.IPv6len-1] |= 0x01 // TODO make this configurable
+		}
+
+		ips := ip.String()
+		log.Infof(" -> resolved %v ...", ips)
+		d.IPAddress = ips
+	} else {
+		log.Infof("Using public network ...")
+		d.IPAddress = srv.Server.PublicNet.IPv4.IP.String()
+	}
+	return nil
+}
diff --git a/driver/placement_groups.go b/driver/placement_groups.go
new file mode 100644
index 0000000000000000000000000000000000000000..d1fd2300818faf3f9f602aabfa515b1936eb86c0
--- /dev/null
+++ b/driver/placement_groups.go
@@ -0,0 +1,85 @@
+package driver
+
+import (
+	"context"
+	"fmt"
+	"github.com/docker/machine/libmachine/log"
+	"github.com/hetznercloud/hcloud-go/hcloud"
+)
+
+const (
+	labelAutoSpreadPg = "auto-spread"
+	labelAutoCreated  = "auto-created"
+	autoSpreadPgName  = "__auto_spread"
+)
+
+func (d *Driver) getAutoPlacementGroup() (*hcloud.PlacementGroup, error) {
+	res, err := d.getClient().PlacementGroup.AllWithOpts(context.Background(), hcloud.PlacementGroupListOpts{
+		ListOpts: hcloud.ListOpts{LabelSelector: d.labelName(labelAutoSpreadPg)},
+	})
+
+	if err != nil {
+		return nil, err
+	}
+
+	if len(res) != 0 {
+		return res[0], nil
+	}
+
+	grp, err := d.makePlacementGroup("Docker-Machine auto spread", map[string]string{
+		d.labelName(labelAutoSpreadPg): "true",
+		d.labelName(labelAutoCreated):  "true",
+	})
+
+	return instrumented(grp), err
+}
+
+func (d *Driver) makePlacementGroup(name string, labels map[string]string) (*hcloud.PlacementGroup, error) {
+	grp, _, err := d.getClient().PlacementGroup.Create(context.Background(), instrumented(hcloud.PlacementGroupCreateOpts{
+		Name:   name,
+		Labels: labels,
+		Type:   "spread",
+	}))
+
+	if grp.PlacementGroup != nil {
+		d.dangling = append(d.dangling, func() {
+			_, err := d.getClient().PlacementGroup.Delete(context.Background(), grp.PlacementGroup)
+			if err != nil {
+				log.Errorf("could not delete placement group: %v", err)
+			}
+		})
+	}
+
+	if err != nil {
+		return nil, fmt.Errorf("could not create placement group: %w", err)
+	}
+
+	return instrumented(grp.PlacementGroup), nil
+}
+
+func (d *Driver) getPlacementGroup() (*hcloud.PlacementGroup, error) {
+	if d.placementGroup == "" {
+		return nil, nil
+	} else if d.cachedPGrp != nil {
+		return d.cachedPGrp, nil
+	}
+
+	name := d.placementGroup
+	if name == autoSpreadPgName {
+		grp, err := d.getAutoPlacementGroup()
+		d.cachedPGrp = grp
+		return grp, err
+	} else {
+		client := d.getClient().PlacementGroup
+		grp, _, err := client.Get(context.Background(), name)
+		if err != nil {
+			return nil, fmt.Errorf("could not get placement group: %w", err)
+		}
+
+		if grp != nil {
+			return grp, nil
+		}
+
+		return d.makePlacementGroup(name, map[string]string{d.labelName(labelAutoCreated): "true"})
+	}
+}
diff --git a/driver/setup.go b/driver/setup.go
new file mode 100644
index 0000000000000000000000000000000000000000..f66e2c2c2f4adea881dcf9fab371f51734e4e21a
--- /dev/null
+++ b/driver/setup.go
@@ -0,0 +1,142 @@
+package driver
+
+import (
+	"context"
+	"github.com/docker/machine/libmachine/state"
+	"github.com/hetznercloud/hcloud-go/hcloud"
+	"github.com/pkg/errors"
+	"os"
+	"time"
+)
+
+func (d *Driver) waitForRunningServer() error {
+	for {
+		srvstate, err := d.GetState()
+		if err != nil {
+			return errors.Wrap(err, "could not get state")
+		}
+
+		if srvstate == state.Running {
+			break
+		}
+
+		time.Sleep(1 * time.Second)
+	}
+	return nil
+}
+
+func (d *Driver) makeCreateServerOptions() (*hcloud.ServerCreateOpts, error) {
+	pgrp, err := d.getPlacementGroup()
+	if err != nil {
+		return nil, err
+	}
+
+	userData, err := d.getUserData()
+	if err != nil {
+		return nil, err
+	}
+
+	srvopts := hcloud.ServerCreateOpts{
+		Name:           d.GetMachineName(),
+		UserData:       userData,
+		Labels:         d.ServerLabels,
+		PlacementGroup: pgrp,
+	}
+
+	err = d.setPublicNetIfRequired(&srvopts)
+	if err != nil {
+		return nil, err
+	}
+
+	networks, err := d.createNetworks()
+	if err != nil {
+		return nil, err
+	}
+	srvopts.Networks = networks
+
+	firewalls, err := d.createFirewalls()
+	if err != nil {
+		return nil, err
+	}
+	srvopts.Firewalls = firewalls
+
+	volumes, err := d.createVolumes()
+	if err != nil {
+		return nil, err
+	}
+	srvopts.Volumes = volumes
+
+	if srvopts.Location, err = d.getLocation(); err != nil {
+		return nil, errors.Wrap(err, "could not get location")
+	}
+	if srvopts.ServerType, err = d.getType(); err != nil {
+		return nil, errors.Wrap(err, "could not get type")
+	}
+	if srvopts.Image, err = d.getImage(); err != nil {
+		return nil, errors.Wrap(err, "could not get image")
+	}
+	key, err := d.getKey()
+	if err != nil {
+		return nil, errors.Wrap(err, "could not get ssh key")
+	}
+	srvopts.SSHKeys = append(d.cachedAdditionalKeys, key)
+	return &srvopts, nil
+}
+
+func (d *Driver) getUserData() (string, error) {
+	file := d.userDataFile
+	if file == "" {
+		return d.userData, nil
+	}
+
+	readUserData, err := os.ReadFile(file)
+	if err != nil {
+		return "", err
+	}
+	return string(readUserData), nil
+}
+
+func (d *Driver) createNetworks() ([]*hcloud.Network, error) {
+	networks := []*hcloud.Network{}
+	for _, networkIDorName := range d.Networks {
+		network, _, err := d.getClient().Network.Get(context.Background(), networkIDorName)
+		if err != nil {
+			return nil, errors.Wrap(err, "could not get network by ID or name")
+		}
+		if network == nil {
+			return nil, errors.Errorf("network '%s' not found", networkIDorName)
+		}
+		networks = append(networks, network)
+	}
+	return instrumented(networks), nil
+}
+
+func (d *Driver) createFirewalls() ([]*hcloud.ServerCreateFirewall, error) {
+	firewalls := []*hcloud.ServerCreateFirewall{}
+	for _, firewallIDorName := range d.Firewalls {
+		firewall, _, err := d.getClient().Firewall.Get(context.Background(), firewallIDorName)
+		if err != nil {
+			return nil, errors.Wrap(err, "could not get firewall by ID or name")
+		}
+		if firewall == nil {
+			return nil, errors.Errorf("firewall '%s' not found", firewallIDorName)
+		}
+		firewalls = append(firewalls, &hcloud.ServerCreateFirewall{Firewall: *firewall})
+	}
+	return instrumented(firewalls), nil
+}
+
+func (d *Driver) createVolumes() ([]*hcloud.Volume, error) {
+	volumes := []*hcloud.Volume{}
+	for _, volumeIDorName := range d.Volumes {
+		volume, _, err := d.getClient().Volume.Get(context.Background(), volumeIDorName)
+		if err != nil {
+			return nil, errors.Wrap(err, "could not get volume by ID or name")
+		}
+		if volume == nil {
+			return nil, errors.Errorf("volume '%s' not found", volumeIDorName)
+		}
+		volumes = append(volumes, volume)
+	}
+	return instrumented(volumes), nil
+}
diff --git a/driver/ssh_keys.go b/driver/ssh_keys.go
new file mode 100644
index 0000000000000000000000000000000000000000..4d60288ed0ace43545c7bb84491dac5dd0b727a0
--- /dev/null
+++ b/driver/ssh_keys.go
@@ -0,0 +1,153 @@
+package driver
+
+import (
+	"context"
+	"fmt"
+	"github.com/docker/machine/libmachine/log"
+	"github.com/docker/machine/libmachine/mcnutils"
+	mcnssh "github.com/docker/machine/libmachine/ssh"
+	"github.com/hetznercloud/hcloud-go/hcloud"
+	"github.com/pkg/errors"
+	"golang.org/x/crypto/ssh"
+	"os"
+)
+
+func (d *Driver) setupExistingKey() error {
+	if !d.IsExistingKey {
+		return nil
+	}
+
+	if d.originalKey == "" {
+		return d.flagFailure("specifying an existing key ID requires the existing key path to be set as well")
+	}
+
+	key, err := d.getKey()
+	if err != nil {
+		return errors.Wrap(err, "could not get key")
+	}
+
+	buf, err := os.ReadFile(d.originalKey + ".pub")
+	if err != nil {
+		return errors.Wrap(err, "could not read public key")
+	}
+
+	// Will also parse `ssh-rsa w309jwf0e39jf asdf` public keys
+	pubk, _, _, _, err := ssh.ParseAuthorizedKey(buf)
+	if err != nil {
+		return errors.Wrap(err, "could not parse authorized key")
+	}
+
+	if key.Fingerprint != ssh.FingerprintLegacyMD5(pubk) &&
+		key.Fingerprint != ssh.FingerprintSHA256(pubk) {
+		return errors.Errorf("remote key %d does not match local key %s", d.KeyID, d.originalKey)
+	}
+
+	return nil
+}
+
+func (d *Driver) copySSHKeyPair(src string) error {
+	if err := mcnutils.CopyFile(src, d.GetSSHKeyPath()); err != nil {
+		return errors.Wrap(err, "could not copy ssh key")
+	}
+
+	if err := mcnutils.CopyFile(src+".pub", d.GetSSHKeyPath()+".pub"); err != nil {
+		return errors.Wrap(err, "could not copy ssh public key")
+	}
+
+	if err := os.Chmod(d.GetSSHKeyPath(), 0600); err != nil {
+		return errors.Wrap(err, "could not set permissions on the ssh key")
+	}
+
+	return nil
+}
+
+func (d *Driver) createRemoteKeys() error {
+	if d.KeyID == 0 {
+		log.Infof("Creating SSH key...")
+
+		buf, err := os.ReadFile(d.GetSSHKeyPath() + ".pub")
+		if err != nil {
+			return errors.Wrap(err, "could not read ssh public key")
+		}
+
+		key, err := d.getRemoteKeyWithSameFingerprint(buf)
+		if err != nil {
+			return errors.Wrap(err, "error retrieving potentially existing key")
+		}
+		if key == nil {
+			log.Infof("SSH key not found in Hetzner. Uploading...")
+
+			key, err = d.makeKey(d.GetMachineName(), string(buf), d.keyLabels)
+			if err != nil {
+				return err
+			}
+		} else {
+			d.IsExistingKey = true
+			log.Debugf("SSH key found in Hetzner. ID: %d", key.ID)
+		}
+
+		d.KeyID = key.ID
+	}
+	for i, pubkey := range d.AdditionalKeys {
+		key, err := d.getRemoteKeyWithSameFingerprint([]byte(pubkey))
+		if err != nil {
+			return errors.Wrapf(err, "error checking for existing key for %v", pubkey)
+		}
+		if key == nil {
+			log.Infof("Creating new key for %v...", pubkey)
+			key, err = d.makeKey(fmt.Sprintf("%v-additional-%d", d.GetMachineName(), i), pubkey, d.keyLabels)
+
+			if err != nil {
+				return errors.Wrapf(err, "error creating new key for %v", pubkey)
+			}
+
+			log.Infof(" -> Created %v", key.ID)
+			d.AdditionalKeyIDs = append(d.AdditionalKeyIDs, key.ID)
+		} else {
+			log.Infof("Using existing key (%v) %v", key.ID, key.Name)
+		}
+
+		d.cachedAdditionalKeys = append(d.cachedAdditionalKeys, key)
+	}
+	return nil
+}
+
+func (d *Driver) prepareLocalKey() error {
+	if d.originalKey != "" {
+		log.Debugf("Copying SSH key...")
+		if err := d.copySSHKeyPair(d.originalKey); err != nil {
+			return errors.Wrap(err, "could not copy ssh key pair")
+		}
+	} else {
+		log.Debugf("Generating SSH key...")
+		if err := mcnssh.GenerateSSHKey(d.GetSSHKeyPath()); err != nil {
+			return errors.Wrap(err, "could not generate ssh key")
+		}
+	}
+	return nil
+}
+
+// Creates a new key for the machine and appends it to the dangling key list
+func (d *Driver) makeKey(name string, pubkey string, labels map[string]string) (*hcloud.SSHKey, error) {
+	keyopts := hcloud.SSHKeyCreateOpts{
+		Name:      name,
+		PublicKey: pubkey,
+		Labels:    labels,
+	}
+
+	key, _, err := d.getClient().SSHKey.Create(context.Background(), instrumented(keyopts))
+	if err != nil {
+		return nil, errors.Wrap(err, "could not create ssh key")
+	} else if key == nil {
+		return nil, errors.Errorf("key upload did not return an error, but key was nil")
+	}
+
+	d.dangling = append(d.dangling, func() {
+		_, err := d.getClient().SSHKey.Delete(context.Background(), key)
+		if err != nil {
+			log.Error(fmt.Errorf("could not delete ssh key: %w", err))
+		}
+	})
+
+	return key, nil
+}
diff --git a/main.go b/main.go
index 33c1af047ccbb93c5618a47d1f3ba54048f111d3..daff94b6ef404d67bedc292c4b373bde4bbae030 100644
--- a/main.go
+++ b/main.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"os"
 
+	"github.com/JonasProgrammer/docker-machine-driver-hetzner/driver"
 	"github.com/docker/machine/libmachine/drivers/plugin"
 )
 
@@ -14,10 +15,9 @@ var version string
 func main() {
 	versionFlag := flag.Bool("v", false, "prints current docker-machine-driver-hetzner version")
 	flag.Parse()
-	instrumented("sadf")
 	if *versionFlag {
 		fmt.Printf("Version: %s\n", version)
 		os.Exit(0)
 	}
-	plugin.RegisterDriver(NewDriver())
+	plugin.RegisterDriver(driver.NewDriver(version))
 }