diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 0000000000000000000000000000000000000000..532c8a8369ef7d24b18c230f75fa38ed9b72c96a
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,189 @@
+  @Library('common-shared') _
+
+  pipeline {
+    agent {
+      kubernetes {
+        label 'buildpack-agent'
+        yaml '''
+        apiVersion: v1
+        kind: Pod
+        spec:
+          containers:
+          - name: buildpack
+            image: buildpack-deps:stable
+            command:
+            - cat
+            tty: true
+            resources:
+              limits:
+                memory: "2Gi"
+                cpu: "1"
+              requests:
+                memory: "2Gi"
+                cpu: "1"
+            volumeMounts:
+            - name: tmp
+              mountPath: /tmp
+          - name: jnlp
+            resources:
+              limits:
+                memory: "2Gi"
+                cpu: "1"
+              requests:
+                memory: "2Gi"
+                cpu: "1"
+            volumeMounts:
+            - name: mvnw
+              mountPath: /home/jenkins/.m2/wrapper
+              readOnly: false
+            - name: m2-repo
+              mountPath: /home/jenkins/.m2/repository
+            - name: settings-xml
+              mountPath: /home/jenkins/.m2/settings.xml
+              subPath: settings.xml
+              readOnly: true
+            - name: tmp
+              mountPath: /tmp
+          volumes:
+          - name: mvnw
+            emptyDir: {}
+          - name: m2-repo
+            emptyDir: {}
+          - name: tmp
+            emptyDir: {}
+          - name: settings-xml
+            secret:
+              secretName: m2-secret-dir
+              items:
+              - key: settings.xml
+                path: settings.xml
+        '''
+      }
+    }
+
+    environment {
+      APP_NAME = 'git-eca-rest-api'
+      NAMESPACE = 'foundation-internal-webdev-apps'
+      IMAGE_NAME = 'eclipsefdn/git-eca-rest-api'
+      CONTAINER_NAME = 'app'
+      ENVIRONMENT = sh(
+        script: """
+          if [ "${env.BRANCH_NAME}" = "master" ]; then
+            printf "production"
+          else
+            printf "${env.BRANCH_NAME}"
+          fi
+        """,
+        returnStdout: true
+      )
+      TAG_NAME = sh(
+        script: """
+          GIT_COMMIT_SHORT=\$(git rev-parse --short ${env.GIT_COMMIT})
+          if [ "${env.ENVIRONMENT}" = "" ]; then
+            printf \${GIT_COMMIT_SHORT}-${env.BUILD_NUMBER}
+          else
+            printf ${env.ENVIRONMENT}-\${GIT_COMMIT_SHORT}-${env.BUILD_NUMBER}
+          fi
+        """,
+        returnStdout: true
+      )
+    }
+
+    options {
+      buildDiscarder(logRotator(numToKeepStr: '10'))
+    }
+
+    stages {
+      stage('Build Java code') {
+        steps {
+          sh './mvnw -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn --batch-mode package'
+          stash includes: 'target/', name: 'target'
+        }
+      }
+
+      stage('Build docker image') {
+        agent {
+          label 'docker-build'
+        }
+        steps {
+          unstash 'target'
+
+          sh 'docker build -f src/main/docker/Dockerfile.jvm --no-cache -t ${IMAGE_NAME}:${TAG_NAME} -t ${IMAGE_NAME}:latest .'
+        }
+      }
+
+      stage('Push docker image') {
+        agent {
+          label 'docker-build'
+        }
+        when {
+          anyOf {
+            environment name: 'ENVIRONMENT', value: 'production'
+            environment name: 'ENVIRONMENT', value: 'staging'
+          }
+        }
+        steps {
+          withDockerRegistry([credentialsId: '04264967-fea0-40c2-bf60-09af5aeba60f', url: 'https://index.docker.io/v1/']) {
+            sh '''
+              docker push ${IMAGE_NAME}:${TAG_NAME}
+              docker push ${IMAGE_NAME}:latest
+            '''
+          }
+        }
+      }
+
+      stage('Deploy to cluster') {
+        agent {
+          kubernetes {
+            label 'kubedeploy-agent'
+            yaml '''
+            apiVersion: v1
+            kind: Pod
+            spec:
+              containers:
+              - name: kubectl
+                image: eclipsefdn/kubectl:1.9-alpine
+                command:
+                - cat
+                tty: true
+            '''
+          }
+        }
+
+        when {
+          anyOf {
+            environment name: 'ENVIRONMENT', value: 'production'
+            environment name: 'ENVIRONMENT', value: 'staging'
+          }
+        }
+        steps {
+          container('kubectl') {
+            withKubeConfig([credentialsId: '1d8095ea-7e9d-4e94-b799-6dadddfdd18a', serverUrl: 'https://console-int.c1-ci.eclipse.org']) {
+              sh '''
+                DEPLOYMENT="$(k8s getFirst deployment "${NAMESPACE}" "app=${APP_NAME},environment=${ENVIRONMENT}")"
+                if [[ $(echo "${DEPLOYMENT}" | jq -r 'length') -eq 0 ]]; then
+                  echo "ERROR: Unable to find a deployment to patch matching 'app=${APP_NAME},environment=${ENVIRONMENT}' in namespace ${NAMESPACE}"
+                  exit 1
+                else 
+                  DEPLOYMENT_NAME="$(echo "${DEPLOYMENT}" | jq -r '.metadata.name')"
+                  kubectl set image "deployment.v1.apps/${DEPLOYMENT_NAME}" -n "${NAMESPACE}" "${CONTAINER_NAME}=${IMAGE_NAME}:${TAG_NAME}" --record=true
+                  if ! kubectl rollout status "deployment.v1.apps/${DEPLOYMENT_NAME}" -n "${NAMESPACE}"; then
+                    # will fail if rollout does not succeed in less than .spec.progressDeadlineSeconds
+                    kubectl rollout undo "deployment.v1.apps/${DEPLOYMENT_NAME}" -n "${NAMESPACE}"
+                    exit 1
+                  fi
+                fi
+              '''
+            }
+          }
+        }
+      }
+    }
+
+    post {
+      always {
+        deleteDir() /* clean up workspace */
+        sendNotifications currentBuild
+      }
+    }
+  }
\ No newline at end of file
diff --git a/src/main/k8s/production.yml b/src/main/k8s/production.yml
index 36afe39ab02c07892aea597fe9a5fa3d101467d6..df8b68e1ee2aede4d12f42bc3eb8a924170a30ff 100644
--- a/src/main/k8s/production.yml
+++ b/src/main/k8s/production.yml
@@ -28,6 +28,15 @@ spec:
           requests:
             cpu: 200m
             memory: 512Mi
+        args: ["-Dconfig.secret.path=/run/secrets/git-eca-rest-api/secret.properties"]
+        volumeMounts:
+        - name: api-oauth-token
+          mountPath: "/run/secrets/git-eca-rest-api"
+          readOnly: true
+      volumes:
+      - name: api-oauth-token
+        secret:
+          secretName: git-eca-rest-api
 ---
 apiVersion: "v1"
 kind: "Service"
diff --git a/src/main/k8s/staging.yml b/src/main/k8s/staging.yml
index 167f449bc20010282e5a3257e58eae557f6b332a..4d8f36dd73d140b2e63221d4ac2cdc64c070a627 100644
--- a/src/main/k8s/staging.yml
+++ b/src/main/k8s/staging.yml
@@ -28,6 +28,15 @@ spec:
           requests:
             cpu: 100m
             memory: 512Mi
+        args: ["-Dconfig.secret.path=/run/secrets/git-eca-rest-api/secret.properties"]
+        volumeMounts:
+        - name: api-oauth-token
+          mountPath: "/run/secrets/git-eca-rest-api"
+          readOnly: true
+      volumes:
+      - name: api-oauth-token
+        secret:
+          secretName: git-eca-rest-api
 ---
 apiVersion: "v1"
 kind: "Service"
diff --git a/src/main/rb/eca.rb b/src/main/rb/eca.rb
index 0a5eab535c5d5b4b7aa12b32a8d28c4906bff08f..3480c0c5819d3284fd1c11493f9fe57697512243 100644
--- a/src/main/rb/eca.rb
+++ b/src/main/rb/eca.rb
@@ -79,8 +79,5 @@ commit_keys.each do |key|
 end
 ## If error, exit as status 1
 if (response.code == 403) then
-  #exit 1
+  exit 1
 end
-
-## Hardcode exit for testing
-exit 1
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 964f9987ea3deb764e18f9bb6e2638b35278deb9..d1068d8d487493e6590b5f659905b9aefcdedde0 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -4,4 +4,8 @@ org.eclipsefoundation.git.eca.api.BotsAPI/mp-rest/url=https://api.eclipse.org
 
 ## OAUTH CONFIG
 oauth2.scope=eclipsefdn_view_all_profiles
-quarkus.http.port=8080
\ No newline at end of file
+quarkus.http.port=8080
+
+## required to start when secret.properties isn't found/mounted
+oauth2.client-id=placeholder
+oauth2.client-secret=placeholder
\ No newline at end of file
diff --git a/src/test/java/org/eclipsefoundation/git/eca/resource/ValidationResourceTest.java b/src/test/java/org/eclipsefoundation/git/eca/resource/ValidationResourceTest.java
index cb335a00633f60b6e92580517bf818c38d8f6864..fbfcb38c3d1e550938952fab016a5da217125599 100644
--- a/src/test/java/org/eclipsefoundation/git/eca/resource/ValidationResourceTest.java
+++ b/src/test/java/org/eclipsefoundation/git/eca/resource/ValidationResourceTest.java
@@ -65,7 +65,7 @@ public class ValidationResourceTest {
 		given()
 			.body(vr)
 			.contentType(ContentType.JSON)
-				.when().post("/git/eca")
+				.when().post("/eca")
 				.then()
 					.statusCode(200)
 					.body("passed", is(true),
@@ -113,7 +113,7 @@ public class ValidationResourceTest {
 		given()
 			.body(vr)
 			.contentType(ContentType.JSON)
-				.when().post("/git/eca")
+				.when().post("/eca")
 				.then()
 					.statusCode(200)
 					.body("passed", is(true),
@@ -147,7 +147,7 @@ public class ValidationResourceTest {
 		given()
 			.body(vr)
 			.contentType(ContentType.JSON)
-				.when().post("/git/eca")
+				.when().post("/eca")
 				.then()
 					.statusCode(200)
 					.body("passed", is(true),
@@ -182,7 +182,7 @@ public class ValidationResourceTest {
 		given()
 			.body(vr)
 			.contentType(ContentType.JSON)
-				.when().post("/git/eca")
+				.when().post("/eca")
 				.then()
 					.statusCode(200)
 					.body("passed", is(true),
@@ -217,7 +217,7 @@ public class ValidationResourceTest {
 		given()
 			.body(vr)
 			.contentType(ContentType.JSON)
-				.when().post("/git/eca")
+				.when().post("/eca")
 				.then()
 					.statusCode(403)
 					.body("passed", is(false),
@@ -254,7 +254,7 @@ public class ValidationResourceTest {
 		given()
 			.body(vr)
 			.contentType(ContentType.JSON)
-				.when().post("/git/eca")
+				.when().post("/eca")
 				.then()
 					.statusCode(403)
 					.body("passed", is(false),
@@ -296,7 +296,7 @@ public class ValidationResourceTest {
 		given()
 			.body(vr)
 			.contentType(ContentType.JSON)
-				.when().post("/git/eca")
+				.when().post("/eca")
 				.then()
 					.statusCode(200)
 					.body("passed", is(true),
@@ -324,7 +324,7 @@ public class ValidationResourceTest {
 		given()
 			.body(vr)
 			.contentType(ContentType.JSON)
-				.when().post("/git/eca")
+				.when().post("/eca")
 				.then()
 					.statusCode(403)
 					.body("passed", is(false),
@@ -366,7 +366,7 @@ public class ValidationResourceTest {
 		given()
 			.body(vr)
 			.contentType(ContentType.JSON)
-				.when().post("/git/eca")
+				.when().post("/eca")
 				.then()
 					.statusCode(200)
 					.body("passed", is(true),
@@ -394,7 +394,7 @@ public class ValidationResourceTest {
 		given()
 			.body(vr)
 			.contentType(ContentType.JSON)
-				.when().post("/git/eca")
+				.when().post("/eca")
 				.then()
 					.statusCode(403)
 					.body("passed", is(false),
@@ -429,7 +429,7 @@ public class ValidationResourceTest {
 		given()
 			.body(vr)
 			.contentType(ContentType.JSON)
-				.when().post("/git/eca")
+				.when().post("/eca")
 				.then()
 					.statusCode(403)
 					.body("passed", is(false),
@@ -468,7 +468,7 @@ public class ValidationResourceTest {
 		given()
 			.body(vr)
 			.contentType(ContentType.JSON)
-				.when().post("/git/eca")
+				.when().post("/eca")
 				.then()
 					.statusCode(403)
 					.body("passed", is(false),
@@ -507,7 +507,7 @@ public class ValidationResourceTest {
 		given()
 			.body(vr)
 			.contentType(ContentType.JSON)
-				.when().post("/git/eca")
+				.when().post("/eca")
 				.then()
 					.statusCode(403)
 					.body("passed", is(false),
diff --git a/src/test/java/org/eclipsefoundation/git/eca/service/impl/MockOAuthService.java b/src/test/java/org/eclipsefoundation/git/eca/service/impl/MockOAuthService.java
new file mode 100644
index 0000000000000000000000000000000000000000..9d86b7c447c5d4bf9c7a43812b960d50b448318a
--- /dev/null
+++ b/src/test/java/org/eclipsefoundation/git/eca/service/impl/MockOAuthService.java
@@ -0,0 +1,24 @@
+package org.eclipsefoundation.git.eca.service.impl;
+
+import org.eclipsefoundation.git.eca.service.OAuthService;
+
+import io.quarkus.test.Mock;
+
+/**
+ * Disable the OAuth service while in testing via a mock service. This will
+ * never authenticate, but since all external data is mocked, this does not
+ * impact testing.
+ * 
+ * @author Martin Lowe
+ *
+ */
+@Mock
+public class MockOAuthService implements OAuthService {
+
+	@Override
+	public String getToken() {
+		// return an empty (invalid) token every time
+		return "";
+	}
+
+}