diff --git a/images/.utils/gitlab-ci.yaml b/images/.utils/gitlab-ci.yaml
index 7d7c2e6e9d0e36b757e80614a174329605bb01cb..4e8e1bf52977bc0ed962d17b0f075bbc701d3d88 100644
--- a/images/.utils/gitlab-ci.yaml
+++ b/images/.utils/gitlab-ci.yaml
@@ -13,6 +13,7 @@ container-build:
           - synadm
           - query-exposer
           - findmydevice-server
+          - opentf
   script:
     - cd images/${IMAGE}
     - |
diff --git a/images/opentf/.release b/images/opentf/.release
new file mode 100644
index 0000000000000000000000000000000000000000..85f180f38cd7bb8259cfadf2e12bfde62fc8f99d
--- /dev/null
+++ b/images/opentf/.release
@@ -0,0 +1 @@
+release=0.1.0
diff --git a/images/opentf/Earthfile b/images/opentf/Earthfile
new file mode 100644
index 0000000000000000000000000000000000000000..fd580da52dc03e1f89f524135a2370328699e7da
--- /dev/null
+++ b/images/opentf/Earthfile
@@ -0,0 +1,29 @@
+VERSION 0.7
+
+build:
+    FROM ../mirror+golang
+    WORKDIR /go-workdir
+    GIT CLONE https://github.com/opentffoundation/opentf.git ./
+    ENV CGO_ENABLED=0
+    RUN go build -mod=readonly -trimpath
+    SAVE ARTIFACT ./opentf ./opentf
+
+container-internal:
+    FROM ../mirror+distroless-static
+    COPY +build/opentf /usr/local/bin/opentf
+    ENTRYPOINT ["/usr/local/bin/opentf"]
+
+container:
+    FROM ../mirror+golang
+    ARG registry=quay.io/shivering-isles/opentf
+    COPY .release ./
+    ARG tag=$(awk -F'=' '$1 == "release" {print $2}' .release)
+    ARG latest=false
+    IF [ $latest = "true" ]
+        FROM +container-internal
+        SAVE IMAGE --push ${registry}:latest
+    ELSE
+        FROM +container-internal
+        LABEL "quay.expires-after"="12w"
+    END
+    SAVE IMAGE --push ${registry}:${tag}