diff --git a/.gitlab-ci.jsonnet b/.gitlab-ci.jsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..1e41d89a81ee843a824da3ac0f64a3a5a3370dda
--- /dev/null
+++ b/.gitlab-ci.jsonnet
@@ -0,0 +1,36 @@
+local jobs = [
+  { name: 'pggat', merge: {} },
+];
+local param_job(image, tag_var, merge={}) = std.mergePatch({
+  stage: 'build',
+  image: {
+    name: 'gcr.io/kaniko-project/executor:debug',
+    entrypoint: [''],
+  },
+  script: [
+    'mkdir -p /kaniko/.docker',
+    @'echo "{\"auths\":{\"${CI_REGISTRY}\":{\"auth\":\"$(printf "%s:%s" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" | base64)\"}}}" > /kaniko/.docker/config.json',
+    std.strReplace(|||
+      /kaniko/executor
+      --context ${CI_PROJECT_DIR}
+      --cache=true
+      --cache-repo="${CI_REGISTRY_IMAGE}/kaniko/cache"
+      --compressed-caching=false
+      --build-arg GOPROXY
+      --registry-mirror=mirror.gfx.cafe
+      --registry-mirror=mirror.gcr.io
+      --registry-mirror=index.docker.io
+      --dockerfile "${CI_PROJECT_DIR}/%(img)s.Dockerfile"
+      --destination "${CI_REGISTRY_IMAGE}/%(img)s:%(tag_var)s"
+      --destination "${CI_REGISTRY_IMAGE}/%(img)s:${CI_COMMIT_SHORT_SHA}"
+      --snapshotMode=redo
+    ||| % { img: image, tag_var: tag_var }, '\n', ' '),
+  ],
+}, merge);
+{
+  [job.name + '-tag']: param_job(job.name, '${CI_COMMIT_TAG}', std.mergePatch(job.merge, { only: ['tags'] }))
+  for job in jobs
+} + {
+  [job.name]: param_job(job.name, '${CI_COMMIT_BRANCH}', job.merge)
+  for job in jobs
+}
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index d43635dae61193f986469ee6f9ef8a622afadfb1..e482eb75e70aac982e6f214fedabd89fe2209ea9 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -9,13 +9,7 @@
 
 stages:
   - test
-
-test:
-  image: golang:1.21-alpine
-  stage: test
-  extends: .go-cache
-  script:
-    - CGO_ENABLED=1 go test -race ./...
+  - build
 
 lint:
   image: registry.gitlab.com/gitlab-org/gitlab-build-images:golangci-lint-alpine
@@ -35,18 +29,22 @@ lint:
     paths:
       - gl-code-quality-report.json
 
-coverage:
-  stage: test
-  image: golang:1.21-alpine
-  coverage: '/\(statements\)(?:\s+)?(\d+(?:\.\d+)?%)/'
-  extends: .go-cache
+jsonnet:
+  stage: build
+  image: alpine:latest
   script:
-    - go run gotest.tools/gotestsum@latest --junitfile report.xml --format testname -- -coverprofile=coverage.txt -covermode count ./...
-    - go tool cover -func=coverage.txt
-    - go run github.com/boumenot/gocover-cobertura@master < coverage.txt > coverage.xml
+    - apk add -U jsonnet
+    - jsonnet .gitlab-ci.jsonnet > generated-config.yml
   artifacts:
-    reports:
-      junit: report.xml
-      coverage_report:
-        coverage_format: cobertura
-        path: coverage.xml
+    paths:
+      - generated-config.yml
+
+trigger-builds:
+  stage: build
+  needs:
+    - jsonnet
+  trigger:
+    include:
+      - artifact: generated-config.yml
+        job: jsonnet
+    strategy: depend
diff --git a/cgat.Dockerfile b/pggat.Dockerfile
similarity index 100%
rename from cgat.Dockerfile
rename to pggat.Dockerfile