diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 78ecfbae15303a6de9fa5622eb36475ba7a9d5e3..f876f479b8d9c9d0e0c05cf9268359523d78163e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -9,20 +9,20 @@ default:
     - 1cpu-4gb
 
 variables:
-  API_DOMAIN_PATH: "$CI_PROJECT_DIR/api_domain"
+  API_DOMAIN_PATH: '$CI_PROJECT_DIR/api_domain'
 
 .deploy:
   image:
     name: 'europe-north1-docker.pkg.dev/holi-shared/docker-hub-remote/hashicorp/terraform:1.6.6'
-    entrypoint: ["/bin/sh", "-c"]
+    entrypoint: ['/bin/sh', '-c']
   variables:
     ENVIRONMENT_ID: $CI_ENVIRONMENT_SLUG
   artifacts:
     paths:
-      - "terraform/environments/crash.log"
-      - "terraform/environments/terraform-*.log"
+      - 'terraform/environments/crash.log'
+      - 'terraform/environments/terraform-*.log'
       - $API_DOMAIN_PATH
-    name: "${CI_JOB_NAME}_${CI_JOB_ID}"
+    name: '${CI_JOB_NAME}_${CI_JOB_ID}'
     expire_in: 1 month
   script:
     - terraform/environments/scripts/create-or-update-env.sh $ENVIRONMENT_ID $CI_COMMIT_SHA
@@ -30,21 +30,29 @@ variables:
   resource_group: $ENVIRONMENT_ID
   interruptible: false
 
+.smoketest:
+  image: 'europe-north1-docker.pkg.dev/holi-shared/docker/holi-docker/holi-k6-builder'
+  script:
+    - 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?
+
 cache_lint:
-  image: 'europe-north1-docker.pkg.dev/holi-shared/docker-hub-remote/denoland/deno:alpine-1.45.2'
-  stage: "test"
+  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
 sast:
-  needs: ["cache_lint"]
+  needs: ['cache_lint']
   stage: test
 include:
   - template: Security/SAST.gitlab-ci.yml
 
 build_docker:
-  needs: ["cache_lint"]
+  needs: ['cache_lint']
   image: 'europe-north1-docker.pkg.dev/holi-shared/docker-hub-remote/docker:27'
   services:
     - 'docker:27-dind'
@@ -60,7 +68,7 @@ build_docker:
 
 staging_deploy:
   extends: .deploy
-  needs: ["build_docker"]
+  needs: ['build_docker']
   environment:
     name: staging
     deployment_tier: staging
@@ -70,19 +78,26 @@ staging_deploy:
   only:
     - main
 
+staging_smoketest:
+  extends: .smoketest
+  needs: ['staging_deploy']
+  only:
+    - main
+
 staging_trigger_unified-api_redeployment:
-  needs: ["staging_deploy"]
+  needs: ['staging_smoketest']
   trigger:
-    project: "app/holi-unified-api"
-    branch: "main"
+    project: 'app/holi-unified-api'
+    branch: 'main'
   only:
     - main
+  resource_group: unified-api-staging
 
 ## production environment
 
 production_deploy:
   extends: .deploy
-  needs: ["build_docker"]
+  needs: ['build_docker']
   allow_failure: false
   environment:
     name: production
@@ -93,10 +108,17 @@ production_deploy:
   only:
     - production
 
+production_smoketest:
+  extends: .smoketest
+  needs: ['production_deploy']
+  only:
+    - production
+
 production_trigger_unified-api_redeployment:
-  needs: ["production_deploy"]
+  needs: ['production_smoketest']
   trigger:
-    project: "app/holi-unified-api"
-    branch: "production"
+    project: 'app/holi-unified-api'
+    branch: 'production'
   only:
     - production
+  resource_group: unified-api-production
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 2f34ebc58adb3df395e52bedee0d670532274122..f4b830c8175179542b86e58cc479dbe443a5adbb 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,7 +1,7 @@
 repos:
--   repo: local
+  - repo: local
     hooks:
-    -   id: gitleaks
+      - id: gitleaks
         name: gitleaks
         language: system
         entry: gitleaks protect --staged -v -c ../.gitleaks.toml
diff --git a/Dockerfile b/Dockerfile
index 79dc6a8835e1c535bd8edd3eb32819d7b94a5f45..e25023558f7b214365d6ca3aa8142bd2d7919e26 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 8089
@@ -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 app/main.ts
+RUN deno cache --allow-import --lock=deno.lock app/deps.ts
+RUN deno cache --allow-import app/main.ts
 
-CMD ["deno", "run", "--allow-net", "--allow-env", "--allow-read", "--lock=deno.lock", "--cached-only", "app/main.ts"]
+CMD ["deno", "run", "--allow-import", "--allow-net", "--allow-env", "--allow-read", "--lock=deno.lock", "--cached-only", "app/main.ts"]
diff --git a/deno.json b/deno.json
index db0d792f0caed9a169e7f35260bb7542d397032c..847307a67c5177408bc6df1ebd772c953a74a30a 100644
--- a/deno.json
+++ b/deno.json
@@ -4,11 +4,11 @@
       "lint": "deno lint",
       "fmt": "deno fmt",
       "fmt:check": "deno fmt --check",
-      "updateDeps": "deno cache --lock=deno.lock --lock-write app/deps.ts app/dev_deps.ts",
-      "install": "deno cache --reload --lock=deno.lock app/deps.ts app/dev_deps.ts",
-      "cache": "deno cache app/main.ts",
-      "dev": "deno run --allow-net --allow-env --allow-read --lock=deno.lock --cached-only --watch app/main.ts",
-      "start": "deno run --allow-net --allow-env --allow-read --lock=deno.lock --cached-only app/main.ts",
+      "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 --allow-read --lock=deno.lock --cached-only --watch app/main.ts",
+      "start": "deno run --allow-import --allow-net --allow-env --allow-read --lock=deno.lock --cached-only app/main.ts",
       "docker": "docker build -t translation-api . && docker run -it --init -p 8089:8089 translation-api",
       "pre-commit": {
         "cmd": "vr lint && vr fmt:check",
diff --git a/smoketest/main.js b/smoketest/main.js
new file mode 100644
index 0000000000000000000000000000000000000000..a6b1e9522ceb86404e400c31544dfaa41ada6a7b
--- /dev/null
+++ b/smoketest/main.js
@@ -0,0 +1,40 @@
+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(`{detectLanguage(text:"Hi")}`, (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,
+      'contains schema description': (r) => typeof r.data.detectLanguage === 'string',
+    })
+  })
+}