diff --git a/inventory/sample/group_vars/all/all.yml b/inventory/sample/group_vars/all/all.yml
index 10479c8e052d0d718b9b07230588abccb1a1a3e9..fb47cc2416acb0c855042b78d1924860d78fd33d 100644
--- a/inventory/sample/group_vars/all/all.yml
+++ b/inventory/sample/group_vars/all/all.yml
@@ -54,7 +54,7 @@ loadbalancer_apiserver_healthcheck_port: 8081
 # cloud_provider:
 
 ## When cloud_provider is set to 'external', you can set the cloud controller to deploy
-## Supported cloud controllers are: 'openstack' and 'vsphere'
+## Supported cloud controllers are: 'openstack', 'vsphere' and 'hcloud'
 ## When openstack or vsphere are used make sure to source in the required fields
 # external_cloud_provider:
 
diff --git a/inventory/sample/group_vars/all/hcloud.yml b/inventory/sample/group_vars/all/hcloud.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ff90dcc8695e7482432df868f2abae37d379d9cd
--- /dev/null
+++ b/inventory/sample/group_vars/all/hcloud.yml
@@ -0,0 +1,14 @@
+## Values for the external Hcloud Cloud Controller
+# external_hcloud_cloud:
+#   hcloud_api_token: ""
+#   token_secret_name: hcloud
+#
+#   service_account_name: cloud-controller-manager
+#
+#   controller_image_tag: "latest"
+#   ## A dictionary of extra arguments to add to the openstack cloud controller manager daemonset
+#   ## Format:
+#   ##  external_hcloud_cloud.controller_extra_args:
+#   ##    arg1: "value1"
+#   ##    arg2: "value2"
+#   controller_extra_args: {}
diff --git a/roles/kubernetes-apps/external_cloud_controller/hcloud/defaults/main.yml b/roles/kubernetes-apps/external_cloud_controller/hcloud/defaults/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5d9ba294bf426a92ab2efe9b56aa36660d39e2da
--- /dev/null
+++ b/roles/kubernetes-apps/external_cloud_controller/hcloud/defaults/main.yml
@@ -0,0 +1,14 @@
+---
+external_hcloud_cloud:
+  hcloud_api_token: ""
+  token_secret_name: hcloud
+
+  service_account_name: cloud-controller-manager
+
+  controller_image_tag: "latest"
+  ## A dictionary of extra arguments to add to the openstack cloud controller manager daemonset
+  ## Format:
+  ##  external_hcloud_cloud.controller_extra_args:
+  ##    arg1: "value1"
+  ##    arg2: "value2"
+  controller_extra_args: {}
diff --git a/roles/kubernetes-apps/external_cloud_controller/hcloud/tasks/main.yml b/roles/kubernetes-apps/external_cloud_controller/hcloud/tasks/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..adaff22194ad8250d3fe077bf259b170e3b34d3b
--- /dev/null
+++ b/roles/kubernetes-apps/external_cloud_controller/hcloud/tasks/main.yml
@@ -0,0 +1,30 @@
+---
+- name: External Hcloud Cloud Controller | Generate Manifests
+  template:
+    src: "{{ item.file }}.j2"
+    dest: "{{ kube_config_dir }}/{{ item.file }}"
+    group: "{{ kube_cert_group }}"
+    mode: 0640
+  with_items:
+    - {name: external-hcloud-cloud-secret, file: external-hcloud-cloud-secret.yml}
+    - {name: external-hcloud-cloud-service-account, file: external-hcloud-cloud-service-account.yml}
+    - {name: external-hcloud-cloud-role-bindings, file: external-hcloud-cloud-role-bindings.yml}
+    - {name: external-hcloud-cloud-controller-manager-ds, file: external-hcloud-cloud-controller-manager-ds.yml}
+    - {name: external-hcloud-cloud-controller-manager-ds-with-networks, file: external-hcloud-cloud-controller-manager-ds-with-networks.yml}
+  register: external_hcloud_manifests
+  when: inventory_hostname == groups['kube_control_plane'][0]
+  tags: external-hcloud
+
+- name: External Hcloud Cloud Controller | Apply Manifests
+  kube:
+    kubectl: "{{ bin_dir }}/kubectl"
+    filename: "{{ kube_config_dir }}/{{ item.item.file }}"
+    state: "latest"
+  with_items:
+    - "{{ external_hcloud_manifests.results }}"
+  when:
+    - inventory_hostname == groups['kube_control_plane'][0]
+    - not item is skipped
+  loop_control:
+    label: "{{ item.item.file }}"
+  tags: external-hcloud
diff --git a/roles/kubernetes-apps/external_cloud_controller/hcloud/templates/external-hcloud-cloud-controller-manager-ds-with-networks.yaml.j2 b/roles/kubernetes-apps/external_cloud_controller/hcloud/templates/external-hcloud-cloud-controller-manager-ds-with-networks.yaml.j2
new file mode 100644
index 0000000000000000000000000000000000000000..3bbe1075301cf297cd049faebc7b99cc8c9f58b5
--- /dev/null
+++ b/roles/kubernetes-apps/external_cloud_controller/hcloud/templates/external-hcloud-cloud-controller-manager-ds-with-networks.yaml.j2
@@ -0,0 +1,69 @@
+---
+apiVersion: apps/v1
+kind: DeamonSet
+metadata:
+  name: hcloud-cloud-controller-manager
+  namespace: kube-system
+  labels:
+    k8s-app: hcloud-cloud-controller-manger
+spec:
+  selector:
+    matchLabels:
+      app: hcloud-cloud-controller-manager
+  template:
+    metadata:
+      labels:
+        app: hcloud-cloud-controller-manager
+      annotations:
+        scheduler.alpha.kubernetes.io/critical-pod: ''
+    spec:
+      serviceAccountName: {{ external_hcloud_cloud.service_account_name }}
+      dnsPolicy: Default
+      tolerations:
+        - key: "node.cloudprovider.kubernetes.io/uninitialized"
+          value: "true"
+          effect: "NoSchedule"
+        - key: "CriticalAddonsOnly"
+          operator: "Exists"
+        - key: "node-role.kubernetes.io/master"
+          effect: NoSchedule
+          operator: Exists
+        - key: "node-role.kubernetes.io/control-plane"
+          effect: NoSchedule
+          operator: Exists
+        - key: "node.kubernetes.io/not-ready"
+          effect: "NoSchedule"
+      hostNetwork: true
+      containers:
+        - image: {{ docker_image_repo }}/hetznercloud/hcloud-cloud-controller-manager:{{ external_hcloud_cloud.controller_image_tag }}
+          name: hcloud-cloud-controller-manager
+          command:
+            - "/bin/hcloud-cloud-controller-manager"
+            - "--cloud-provider=hcloud"
+            - "--leader-elect=false"
+            - "--allow-untagged-cloud"
+            - "--allocate-node-cidrs=true"
+            - "--cluster-cidr=10.244.0.0/16"
+          args:
+{% for key, value in external_hcloud_cloud.controller_extra_args.items() %}
+            - "{{ '--' + key + '=' + value }}"
+{% endfor %}
+          resources:
+            requests:
+              cpu: 100m
+              memory: 50Mi
+          env:
+            - name: NODE_NAME
+              valueFrom:
+                fieldRef:
+                  fieldPath: spec.nodeName
+            - name: HCLOUD_TOKEN
+              valueFrom:
+                secretKeyRef:
+                  name: hcloud
+                  key: token
+            - name: HCLOUD_NETWORK
+              valueFrom:
+                secretKeyRef:
+                  name: {{ external_hcloud_cloud.token_secret_name }}
+                  key: {{ external_hcloud_cloud.token_secret_key }}
diff --git a/roles/kubernetes-apps/external_cloud_controller/hcloud/templates/external-hcloud-cloud-controller-manager-ds.yaml.j2 b/roles/kubernetes-apps/external_cloud_controller/hcloud/templates/external-hcloud-cloud-controller-manager-ds.yaml.j2
new file mode 100644
index 0000000000000000000000000000000000000000..fecee8d0afe3a131f436711faf29101c6f6d5f6c
--- /dev/null
+++ b/roles/kubernetes-apps/external_cloud_controller/hcloud/templates/external-hcloud-cloud-controller-manager-ds.yaml.j2
@@ -0,0 +1,61 @@
+---
+apiVersion: apps/v1
+kind: DeamonSet
+metadata:
+  name: hcloud-cloud-controller-manager
+  namespace: kube-system
+  labels:
+    k8s-app: hcloud-cloud-controller-manger
+spec:
+  selector:
+    matchLabels:
+      app: hcloud-cloud-controller-manager
+  updateStrategy:
+    type: RollingUpdate
+  template:
+    metadata:
+      labels:
+        app: hcloud-cloud-controller-manager
+      annotations:
+        scheduler.alpha.kubernetes.io/critical-pod: ''
+    spec:
+      serviceAccountName: {{ external_hcloud_cloud.service_account_name }}
+      dnsPolicy: Default
+      tolerations:
+        - key: "node.cloudprovider.kubernetes.io/uninitialized"
+          value: "true"
+          effect: "NoSchedule"
+        - key: "CriticalAddonsOnly"
+          operator: "Exists"
+        - key: "node-role.kubernetes.io/master"
+          effect: NoSchedule
+        - key: "node-role.kubernetes.io/control-plane"
+          effect: NoSchedule
+        - key: "node.kubernetes.io/not-ready"
+          effect: "NoSchedule"
+      containers:
+        - image: {{ docker_image_repo }}/hetznercloud/hcloud-cloud-controller-manager:{{ external_hcloud_cloud.controller_image_tag }}
+          name: hcloud-cloud-controller-manager
+          command:
+            - "/bin/hcloud-cloud-controller-manager"
+            - "--cloud-provider=hcloud"
+            - "--leader-elect=false"
+            - "--allow-untagged-cloud"
+          args:
+{% for key, value in external_hcloud_cloud.controller_extra_args.items() %}
+            - "{{ '--' + key + '=' + value }}"
+{% endfor %}
+          resources:
+            requests:
+              cpu: 100m
+              memory: 50Mi
+          env:
+            - name: NODE_NAME
+              valueFrom:
+                fieldRef:
+                  fieldPath: spec.nodeName
+            - name: HCLOUD_TOKEN
+              valueFrom:
+                secretKeyRef:
+                  name: {{ external_hcloud_cloud.token_secret_name }}
+                  key: {{ external_hcloud_cloud.token_secret_key }}
diff --git a/roles/kubernetes-apps/external_cloud_controller/hcloud/templates/external-hcloud-cloud-role-bindings.yml.j2 b/roles/kubernetes-apps/external_cloud_controller/hcloud/templates/external-hcloud-cloud-role-bindings.yml.j2
new file mode 100644
index 0000000000000000000000000000000000000000..270c947b585d2be8b04e0beafaa092c5d85c4774
--- /dev/null
+++ b/roles/kubernetes-apps/external_cloud_controller/hcloud/templates/external-hcloud-cloud-role-bindings.yml.j2
@@ -0,0 +1,13 @@
+---
+kind: ClusterRoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: system:cloud-controller-manager
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: cluster-admin
+subjects:
+  - kind: ServiceAccount
+    name: {{ external_hcloud_cloud.service_account_name }}
+    namespace: kube-system
diff --git a/roles/kubernetes-apps/external_cloud_controller/hcloud/templates/external-hcloud-cloud-secret.yml.j2 b/roles/kubernetes-apps/external_cloud_controller/hcloud/templates/external-hcloud-cloud-secret.yml.j2
new file mode 100644
index 0000000000000000000000000000000000000000..614d278978c766b4a2bae964777b6237a5770492
--- /dev/null
+++ b/roles/kubernetes-apps/external_cloud_controller/hcloud/templates/external-hcloud-cloud-secret.yml.j2
@@ -0,0 +1,8 @@
+---
+apiVersion: v1
+kind: Secret
+metadata:
+  name: "{{ external_hcloud_cloud.token_secret_name }}"
+  namespace: kube-system
+data:
+  token: "{{ external_hcloud_cloud.hcloud_api_token | base64 }}"
diff --git a/roles/kubernetes-apps/external_cloud_controller/hcloud/templates/external-hcloud-cloud-service-account.yml.j2 b/roles/kubernetes-apps/external_cloud_controller/hcloud/templates/external-hcloud-cloud-service-account.yml.j2
new file mode 100644
index 0000000000000000000000000000000000000000..93277dddd30b190e780fcf192c155a00bf0014e7
--- /dev/null
+++ b/roles/kubernetes-apps/external_cloud_controller/hcloud/templates/external-hcloud-cloud-service-account.yml.j2
@@ -0,0 +1,6 @@
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: {{ external_hcloud_cloud.service_account_name }}
+  namespace: kube-system
diff --git a/roles/kubernetes-apps/external_cloud_controller/meta/main.yml b/roles/kubernetes-apps/external_cloud_controller/meta/main.yml
index a75a42995feb6ad048130356a3875ea0965bf9b7..6e8c235dd6542fb1610fd3ab56182b7c10c9de81 100644
--- a/roles/kubernetes-apps/external_cloud_controller/meta/main.yml
+++ b/roles/kubernetes-apps/external_cloud_controller/meta/main.yml
@@ -20,3 +20,13 @@ dependencies:
     tags:
       - external-cloud-controller
       - external-vsphere
+  - role: kubernetes-apps/external_cloud_controller/hcloud
+    when:
+      - cloud_provider is defined
+      - cloud_provider == "external"
+      - external_cloud_provider is defined
+      - external_cloud_provider == "hcloud"
+      - inventory_hostname == groups['kube_control_plane'][0]
+    tags:
+      - external-cloud-controller
+      - external-hcloud
diff --git a/roles/kubespray-defaults/defaults/main.yaml b/roles/kubespray-defaults/defaults/main.yaml
index fc5467b6a5281872e22176746fb2cfcf31ca3f95..c9f6b801155c2d4e0f8f28a5f95d85848564247d 100644
--- a/roles/kubespray-defaults/defaults/main.yaml
+++ b/roles/kubespray-defaults/defaults/main.yaml
@@ -438,6 +438,21 @@ external_openstack_lbaas_use_octavia: false
 external_openstack_network_internal_networks: []
 external_openstack_network_public_networks: []
 
+# Default values for the external Hcloud Cloud Controller
+external_hcloud_cloud:
+  hcloud_api_token: ""
+  token_secret_name: hcloud
+
+  service_account_name: cloud-controller-manager
+
+  controller_image_tag: "latest"
+  ## A dictionary of extra arguments to add to the openstack cloud controller manager daemonset
+  ## Format:
+  ##  external_hcloud_cloud.controller_extra_args:
+  ##    arg1: "value1"
+  ##    arg2: "value2"
+  controller_extra_args: {}
+
 ## List of authorization modes that must be configured for
 ## the k8s cluster. Only 'AlwaysAllow', 'AlwaysDeny', 'Node' and
 ## 'RBAC' modes are tested. Order is important.