diff --git a/.envrc b/.envrc
index 4f0af3a80ef9b7d25eaa8177ee64bcd7310af8b4..3b5bb1416c85df12ee45836120a60c4dcab29a79 100644
--- a/.envrc
+++ b/.envrc
@@ -7,6 +7,3 @@ fi
 
 # loads personal (secret) data from separate env file (not checked in)
 source_env_if_exists .envrc.local
-
-type yarn >/dev/null 2>&1 && PATH="$PATH:$(yarn global bin)"
-export PATH
\ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index a05bc5929aeba22e57414b8455631cb825ae132e..7ac02c1134466889944302a2290485dc9fdc75bd 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -4,7 +4,7 @@ default:
     - env
   interruptible: true
   tags:
-    - holi-small # build on smaller machine
+    - 1cpu-4gb # build on smaller machine
 
 variables:
   API_DOMAIN_PATH: "$CI_PROJECT_DIR/api_domain"
diff --git a/.husky/pre-commit b/.husky/pre-commit
deleted file mode 100755
index c1d699695f34ed0a0d789b795412ef67013e7458..0000000000000000000000000000000000000000
--- a/.husky/pre-commit
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/sh
-. "$(dirname "$0")/_/husky.sh"
-
-gitleaks protect --staged -v -c ../.gitleaks.toml
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..2f34ebc58adb3df395e52bedee0d670532274122
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,9 @@
+repos:
+-   repo: local
+    hooks:
+    -   id: gitleaks
+        name: gitleaks
+        language: system
+        entry: gitleaks protect --staged -v -c ../.gitleaks.toml
+        pass_filenames: false
+        always_run: true
diff --git a/app/common_test.ts b/app/common_test.ts
index 7c29193eb1df30dff9cee182602c8027ca998ca5..ddbf135ffc9d7859ded40537c03cc63eb7db13d9 100644
--- a/app/common_test.ts
+++ b/app/common_test.ts
@@ -49,6 +49,7 @@ export const processGqlRequest = (
     headers: {
       "content-type": "application/json",
       "accept": "*/*",
+      ...headers,
     },
     body: JSON.stringify({
       operationName: null,
diff --git a/app/goodnews.ts b/app/goodnews.ts
index c2554a5aa5a41fcbffe96d8a910fb82339022960..b0564b883b2a53ba14b6eb44b5738e110b7bc1ed 100644
--- a/app/goodnews.ts
+++ b/app/goodnews.ts
@@ -92,7 +92,7 @@ export const toGoodNewsArticle = (
 
 const fetchPage =
   (config: ContentfulConfig) =>
-  (skip: number, limit: number): Promise<ContentfulEntriesResponse> => {
+  async (skip: number, limit: number): Promise<ContentfulEntriesResponse> => {
     // baseUrl used for logging to not leak access_token
     const baseUrl =
       `${config.baseUrl}/${config.spaceId}/environments/${config.environment}/entries`;
@@ -105,13 +105,17 @@ const fetchPage =
     logger.info(`fetching articles ${skip} to ${skip + limit} from ${baseUrl}`);
 
     const start = Date.now();
-    return fetch(url)
-      .then((result) => result.json())
-      .then((result) => {
-        const duration = Date.now() - start;
-        logger.debug(`fetching articles took ${duration} ms`);
-        return result;
-      });
+    try {
+      const fetchResult = await fetch(url);
+      const resultJson = await fetchResult.json();
+      return resultJson;
+    } catch (err) {
+      logger.error("fetching articles failed: " + err.message);
+      throw err;
+    } finally {
+      const duration = Date.now() - start;
+      logger.info(`fetching articles took ${duration} ms`);
+    }
   };
 
 type ContentfulItemFilter = (item: ContentfulItem) => boolean;
diff --git a/app/server.ts b/app/server.ts
index 3775eab303e25caf5cede53beadc7de792784f67..600b36950a140b7e465b4c77a7d614b028d138a0 100644
--- a/app/server.ts
+++ b/app/server.ts
@@ -61,19 +61,25 @@ const createResolvers = (config: ContentfulConfig) => {
       ): Promise<GoodNewsArticleQueryResponse> => {
         return config.fake
           ? Promise.resolve({ articles: [], previousDateTeaser: undefined })
-          : executeArticlesQuery(config)(parameters, context.language);
+          : executeArticlesQuery(config)(
+            parameters,
+            getLanguage(context?.request?.headers),
+          );
       },
     },
   });
 };
 
-const getLanguage = (languages = "") =>
-  languages
+const getLanguage = (headers?: Headers) => {
+  const languages: string = headers?.get("accept-language") || DEFAULT_LANGUAGE;
+  return (languages
     .split(",") // languages are comma separated
     .map((language) => language.split("-")[0]) // languages may include country code 'de-DE' e.g.
     .map((language) => language.split(";")[0]) // languages may include a 'de;q=0.6' e.g.
+    .map((language) => language.trim())
     .find((language) => SUPPORTED_LANGUAGES.includes(language)) ||
-  DEFAULT_LANGUAGE;
+    DEFAULT_LANGUAGE) as GoodNewsLanguage;
+};
 
 export const DEFAULT_PORT = 8002;
 export const DEFAULT_CACHE_ENABLED = true;
@@ -103,10 +109,7 @@ export const createGraphQLServer = (config: ServerConfig): GraphQLServer => {
     ? [
       useResponseCache({
         // global cache per language, shared by all users
-        session: (request: Request) =>
-          getLanguage(
-            request.headers.get("accept-language") || DEFAULT_LANGUAGE,
-          ),
+        session: (request: Request) => getLanguage(request.headers),
         ttlPerSchemaCoordinate: {
           "Query.articles": config.cacheTtlMs || DEFAULT_CACHE_TTL_MS,
         },
@@ -118,14 +121,6 @@ export const createGraphQLServer = (config: ServerConfig): GraphQLServer => {
     schema: createSchema({ resolvers, typeDefs }),
     graphiql: true,
     plugins,
-    context: (context: GraphQLContext) => {
-      const headers = new Headers(context.params?.extensions?.headers);
-      const languages = headers.get("accept-language") || DEFAULT_LANGUAGE;
-      return {
-        ...context,
-        language: getLanguage(languages),
-      };
-    },
   });
 };
 
@@ -133,6 +128,7 @@ export const createGraphQLServer = (config: ServerConfig): GraphQLServer => {
 export type GraphQLServer = any;
 
 export type GraphQLContext = {
+  request?: { headers: Headers };
   params?: {
     extensions?: {
       headers?: {
@@ -140,12 +136,11 @@ export type GraphQLContext = {
       };
     };
   };
-  language: GoodNewsLanguage;
 };
 
 export const startServer = (config: ServerConfig): Promise<void> => {
   const graphQLServer: GraphQLServer = createGraphQLServer(config);
-  return serve(graphQLServer.handleRequest, {
+  return serve(graphQLServer, {
     port: config.port || DEFAULT_PORT,
     onListen({ port, hostname }) {
       logger.info(
diff --git a/terraform/environments/deployment.tf b/terraform/environments/deployment.tf
index 1d1db0e18b99fafd36ea4fa7d8b9261faf721305..83caa0b21044c47a22d7ff529c190ee897e269e4 100644
--- a/terraform/environments/deployment.tf
+++ b/terraform/environments/deployment.tf
@@ -54,8 +54,7 @@ resource "google_cloud_run_service" "goodnews_api" {
         }
         env {
           name  = "CACHE_TTL_MS"
-          value = local.environment == "production" ? "43200000" : "60000"
-          # 12 hours for production, otherwise 1 minute
+          value = local.environment == "production" ? "43200000" : "43200000"
         }
         env {
           name = "CONTENTFUL_BASE_URL"
@@ -100,7 +99,7 @@ resource "google_cloud_run_service" "goodnews_api" {
             memory = local.environment == "production" ? "512Mi" : "256Mi"
           }
           requests = {
-            cpu    = local.environment == "production" ? "1000m" : "10m"
+            cpu    = local.environment == "production" ? "1000m" : "1000m"
             memory = local.environment == "production" ? "512Mi" : "256Mi"
           }
         }