diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 376a5af8419e10fd43c4d8aa94567017299a780e..603df8628df059ddbc1691f945e9bc9f6e1d682e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -34,12 +34,13 @@ variables:
   resource_group: $ENVIRONMENT_ID # never execute terraform in parallel on the same environment
   interruptible: false
 
-.e2e:
-  image: 'europe-north1-docker.pkg.dev/holi-shared/docker-hub-remote/archlinux:latest'
+.smoketest:
+  image: 'europe-north1-docker.pkg.dev/holi-shared/docker/holi-docker/holi-k6-builder'
   script:
-    - API_DOMAIN=`cat $API_DOMAIN_PATH`
-    - echo "e2e tests against $CI_ENVIRONMENT_SLUG environment go here and against $API_DOMAIN"
+    - API_DOMAIN=$(cat $API_DOMAIN_PATH)
     - terraform/environments/scripts/wait-for-ssl.sh "https://${API_DOMAIN}"
+    - BASE_URL="https://${API_DOMAIN}/graphql" k6 run smoketest/main.js
+    # TODO should/could we roll back the service to the last working revision on test failure?
 
 # end job templates
 
@@ -48,13 +49,13 @@ variables:
 ## common steps
 
 cache_lint_test:
-  image: 'europe-north1-docker.pkg.dev/holi-shared/docker-hub-remote/denoland/deno:alpine-1.45.2'
+  image: 'europe-north1-docker.pkg.dev/holi-shared/docker-hub-remote/denoland/deno:2.0.0'
   stage: 'test'
   script:
-    - deno cache --lock=deno.lock app/deps.ts app/dev_deps.ts
+    - deno cache --allow-import --lock=deno.lock app/deps.ts app/dev_deps.ts
     - deno fmt --check
     - deno lint
-    - deno test
+    - deno test --allow-import
 
 # You can override the included template(s) by including variable overrides
 # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
@@ -98,8 +99,8 @@ review_deploy:
     - production
     - /^noenv\/.*/
 
-review_e2e:
-  extends: .e2e
+review_smoketest:
+  extends: .smoketest
   needs: ['review_deploy']
   except:
     - main
@@ -134,7 +135,7 @@ review_destroy:
     - terraform/environments/scripts/destroy-env.sh $CI_ENVIRONMENT_SLUG
   # can't use rules here: https://gitlab.com/gitlab-org/gitlab/-/issues/34077
   when: manual
-  allow_failure: false
+  allow_failure: true
   except:
     - main
     - production
@@ -156,19 +157,18 @@ staging_deploy:
   only:
     - main
 
-staging_trigger_unified-api_redeployment:
+staging_smoketest:
+  extends: .smoketest
   needs: ['staging_deploy']
-  trigger:
-    project: 'app/holi-unified-api'
-    branch: 'main'
   only:
     - main
+  resource_group: unified-api-staging
 
-staging_e2e:
-  extends: .e2e
-  # if staging_e2e would actually run tests (other than ensuring SSL works), we'd have to
-  # wait for the unified-api pipeline to finish.
-  needs: ['staging_deploy']
+staging_trigger_unified-api_redeployment:
+  needs: ['staging_smoketest']
+  trigger:
+    project: 'app/holi-unified-api'
+    branch: 'main'
   only:
     - main
 
@@ -187,10 +187,17 @@ production_deploy:
   only:
     - production
 
-production_trigger_unified-api_redeployment:
+production_smoketest:
+  extends: .smoketest
   needs: ['production_deploy']
+  only:
+    - production
+
+production_trigger_unified-api_redeployment:
+  needs: ['production_smoketest']
   trigger:
     project: 'app/holi-unified-api'
     branch: 'production'
   only:
     - production
+  resource_group: unified-api-production
diff --git a/Dockerfile b/Dockerfile
index f816291010446e6f4eac386b0bd36ec11a5da321..f5cdeecc393eedd1154db5db008f0e336d28fd63 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM europe-north1-docker.pkg.dev/holi-shared/docker-hub-remote/denoland/deno:alpine-1.45.2
+FROM europe-north1-docker.pkg.dev/holi-shared/docker-hub-remote/denoland/deno:2.0.0
 
 # The port that your application listens to.
 EXPOSE 8003
@@ -10,7 +10,7 @@ COPY deno.lock .
 COPY deno.json .
 COPY ./app ./app
 
-RUN deno cache --lock=deno.lock app/deps.ts
-RUN deno cache --lock=deno.lock app/main.ts
+RUN deno cache --allow-import --lock=deno.lock app/deps.ts
+RUN deno cache --allow-import --lock=deno.lock app/main.ts
 
-CMD ["deno", "run", "--allow-net", "--allow-env", "--lock=deno.lock", "--cached-only", "app/main.ts"]
+CMD ["deno", "run", "--allow-import", "--allow-net", "--allow-env", "--lock=deno.lock", "--cached-only", "app/main.ts"]
diff --git a/deno.json b/deno.json
index f97c7be6c705d3b0f88441c0cdf2d2f9828bfc70..73b2a977088ad1611911c1b382a515c688606836 100644
--- a/deno.json
+++ b/deno.json
@@ -4,14 +4,14 @@
       "lint": "deno lint",
       "fmt": "deno fmt",
       "fmt:check": "deno fmt --check",
-      "test": "deno test",
+      "test": "deno test --allow-import ",
       "updateDeps": "deno cache --allow-import --lock=deno.lock --lock-write app/deps.ts app/dev_deps.ts",
       "install": "deno cache --allow-import --reload --lock=deno.lock app/deps.ts app/dev_deps.ts",
       "cache": "deno cache --allow-import app/main.ts",
       "dev": "deno run --allow-import --allow-net --allow-env --lock=deno.lock --cached-only --watch app/main.ts",
       "start": "deno run --allow-import --allow-net --allow-env --lock=deno.lock --cached-only app/main.ts",
       "docker": "docker build -t geo-service . && docker run -it --init -p 8003:8003 geo-service",
-      "coverage": "deno test --coverage=coverage && deno coverage coverage",
+      "coverage": "deno test --allow-import --coverage=coverage && deno coverage coverage",
       "pre-commit": {
         "cmd": "vr lint && vr fmt:check",
         "gitHook": "pre-commit"
@@ -22,6 +22,8 @@
     "lineWidth": 120,
     "singleQuote": true,
     "semiColons": false,
-    "exclude": ["*.md"]
+    "exclude": [
+      "*.md"
+    ]
   }
 }
diff --git a/smoketest/main.js b/smoketest/main.js
new file mode 100644
index 0000000000000000000000000000000000000000..a98991b9a9bc754c748a373e9389b58f4f0cccfe
--- /dev/null
+++ b/smoketest/main.js
@@ -0,0 +1,43 @@
+import http from 'k6/http'
+import { check } from 'k6'
+
+// You don't need to change anything in this section, it's k6 glue code.
+// See the default function at the end of the file for defining your smoketest.
+// This configuration only executes 1 test, enough for a smoketest. The smoketest will fail on any check failing.
+const allChecksNeedToPassTreshold = { checks: [{ threshold: 'rate==1', abortOnFail: true }] }
+export const options = {
+  vus: 1,
+  iterations: 1,
+  thresholds: allChecksNeedToPassTreshold,
+}
+
+/**
+ * Performs a GraphQL query and checks the response using the provided function. Fails if any of the provided expectations are not met.
+ * @param {string} query The GraphQL query to perform
+ * @param {(response: http.Response) => Array<boolean>} checkFunction
+ *   A function that takes the HTTP response as an argument and returns an array
+ *   of boolean values, each indicating success or failure of a test.
+ */
+function forQuery(query, checkFunction) {
+  const response = http.post(`${__ENV.BASE_URL}`, JSON.stringify({ query }), {
+    headers: { 'Content-Type': 'application/json' },
+  })
+  checkFunction(response)
+}
+
+// Define your smoketest(s) here.
+export default () => {
+  forQuery(
+    `{placeDetails(id:"51223999b85500244059f052ea9271c64a40f00101f9013ef5000000000000c00208"){name}}`,
+    (response) => {
+      check(response, {
+        'is status 200': (r) => r.status === 200,
+      })
+      check(JSON.parse(response.body), {
+        // there can be multiple tests here, e.g.
+        //"contains topics object": (r) => typeof r.data.topics != null,
+        'returns name for place': (r) => typeof r.data.placeDetails.name === 'string',
+      })
+    },
+  )
+}
diff --git a/terraform/environments/deployment.tf b/terraform/environments/deployment.tf
index cc2fb8aa661111886f1c6135654d3fccc6a8d6d4..34f9b14c3baf6d27d0c53d8266e2d163a576755f 100644
--- a/terraform/environments/deployment.tf
+++ b/terraform/environments/deployment.tf
@@ -82,6 +82,7 @@ resource "google_cloud_run_service" "geo_api" {
         "run.googleapis.com/vpc-access-connector" = data.terraform_remote_state.holi_infra_state.outputs.vpc_access_connector_name
         # possible values: all-traffic/private-ranges-only(default) https://cloud.google.com/sdk/gcloud/reference/run/services/update#--vpc-egress
         "run.googleapis.com/vpc-access-egress" = "private-ranges-only"
+        "run.googleapis.com/startup-cpu-boost"    = "true"
       }
       # labels set on the revision level
       labels = {