diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 0000000000000000000000000000000000000000..e21877af5765c949456767bec78c2b93c70380bb
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,98 @@
+@Library('common-shared') _
+
+pipeline {
+  agent {
+    kubernetes {
+      label 'kubedeploy-agent'
+      yaml '''
+      apiVersion: v1
+      kind: Pod
+      spec:
+        containers:
+        - name: kubectl
+          image: eclipsefdn/kubectl:1.14-alpine
+          command:
+          - cat
+          tty: true
+      '''
+    }
+  }
+
+  environment {
+    NAMESPACE = 'foundation-internal-webdev-apps'
+    IMAGE_NAME = 'eclipsefdn/eclipsefdn-github-sync'
+    CRONJOB_NAME = 'eclipsefdn-github-sync'
+    CONTAINER_NAME = 'eclipsefdn-github-sync'
+    TAG_NAME = sh(
+      script: """
+        GIT_COMMIT_SHORT=\$(git rev-parse --short ${env.GIT_COMMIT})
+        printf \${GIT_COMMIT_SHORT}-${env.BUILD_NUMBER}
+      """,
+      returnStdout: true
+    )
+  }
+
+  options {
+    buildDiscarder(logRotator(numToKeepStr: '10'))
+  }
+
+  triggers { 
+    // build once a week to keep up with parents images updates
+    cron('H H * * H') 
+  }
+
+  stages {
+    stage('Build docker image') {
+      agent {
+        label 'docker-build'
+      }
+      steps {
+        sh '''
+          docker build --pull -t ${IMAGE_NAME}:${TAG_NAME} -t ${IMAGE_NAME}:latest .
+        '''
+      }
+    }
+
+    stage('Push docker image') {
+      agent {
+        label 'docker-build'
+      }
+      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') {
+      when {
+        branch 'master'
+      }
+      steps {
+        container('kubectl') {
+          withKubeConfig([credentialsId: '1d8095ea-7e9d-4e94-b799-6dadddfdd18a', serverUrl: 'https://console-int.c1-ci.eclipse.org']) {
+            sh '''
+              CRONJOB="$(kubectl get cronjob ${CRONJOB_NAME} -n "${NAMESPACE}" -o json)"
+              if [[ $(echo "${CRONJOB}" | jq -r 'length') -eq 0 ]]; then
+                echo "ERROR: Unable to find a cronjob to patch matching name '${CRONJOB_NAME}' in namespace ${NAMESPACE}"
+                exit 1
+              else 
+                kubectl set image "cronjob.v1beta1.batch/${CRONJOB_NAME}" -n "${NAMESPACE}" "${CONTAINER_NAME}=${IMAGE_NAME}:${TAG_NAME}" --record=true
+              fi
+            '''
+          }
+        }
+      }
+    }
+  }
+
+  post {
+    always {
+      deleteDir() /* clean up workspace */
+      sendNotifications currentBuild
+    }
+  }
+}
diff --git a/k8s/cronjob.yml b/k8s/cronjob.yml
new file mode 100644
index 0000000000000000000000000000000000000000..726790b0d50f8184f45d2df902c99e596a003216
--- /dev/null
+++ b/k8s/cronjob.yml
@@ -0,0 +1,75 @@
+apiVersion: batch/v1beta1
+kind: CronJob
+metadata:
+  name: eclipsefdn-github-sync
+  namespace: foundation-internal-webdev-apps
+spec:
+  schedule: "*/20 * * * *"
+  # if it is time for a new job run and the previous job run hasn’t finished yet, the cron job skips the new job run
+  concurrencyPolicy: Forbid
+  jobTemplate:
+    spec:
+      template:
+        spec:
+          containers:
+          - name: eclipsefdn-github-sync
+            image: eclipsefdn/eclipsefdn-github-sync:latest
+            imagePullPolicy: Always # TODO: change to IfNotPresent once the deploy step is integrated into CD
+            args:
+            - /bin/sh
+            - -c
+            - npm start -- --verbose=true --dryrun=true | tee -a /app/logs/stdout-$(date +%Y%m%d).log
+            volumeMounts:
+            - name: logs
+              mountPath: /app/logs
+            - name: cache
+              mountPath: /app/.cache
+            - name: github-sync-secrets
+              mountPath: "/run/secrets/api-token"
+              readOnly: true
+              # workaround https://github.com/kubernetes/kubernetes/issues/65835
+              subPath: api-token
+          restartPolicy: Never
+          volumes:
+          - name: logs
+            persistentVolumeClaim:
+              claimName: github-sync-logs
+          - name: cache
+            emptyDir: {}
+          - name: github-sync-secrets
+            secret:
+              secretName: github-sync-secrets
+              # workaround https://github.com/kubernetes/kubernetes/issues/65835
+              items:
+              - key: api-token
+                path: api-token
+---
+apiVersion: v1
+kind: PersistentVolume
+metadata:
+  name: github-sync-logs
+  annotations:
+    volume.beta.kubernetes.io/mount-options: rw,nfsvers=3,noexec
+spec:
+  capacity:
+    storage: 10Gi
+  accessModes:
+    - ReadWriteOnce
+  claimRef:
+    namespace: foundation-internal-webdev-apps
+    name: github-sync-logs
+  nfs:
+    server: fred
+    path: /opt/export/eclipsefdn-github-sync/logs
+---
+kind: PersistentVolumeClaim
+apiVersion: v1
+metadata:
+  name: github-sync-logs
+  namespace: foundation-internal-webdev-apps
+spec:
+  accessModes:
+    - ReadWriteOnce
+  resources:
+    requests:
+      storage: 10Gi
\ No newline at end of file