diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index c4ca76a66..4c531e80e 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,3 +1,4 @@
 *  @prometheus-operator/prometheus-operator-reviewers
 
 /scripts/ @paulfantom
+/.github/workflows/ @paulfantom
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 000000000..0040fb9f3
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,129 @@
+name: ci
+on:
+  - push
+  - pull_request
+env:
+  golang-version: '1.14'
+  kind-version: 'v0.8.1'
+  kind-image: 'kindest/node:v1.18.8'  # Image defines which k8s version is used
+jobs:
+  generate:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        os:
+          - macos-latest
+          - ubuntu-latest
+    name: Generate and format
+    steps:
+    - uses: actions/checkout@v2
+    - uses: actions/setup-go@v2
+      with:
+        go-version: ${{ env.golang-version }}
+    - run: make --always-make format generate && git diff --exit-code
+  build:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        os:
+          - macos-latest
+          - ubuntu-latest
+    name: Build operator binary
+    steps:
+    - uses: actions/checkout@v2
+    - uses: actions/setup-go@v2
+      with:
+        go-version: ${{ env.golang-version }}
+    - run: make operator
+  po-rule-migration:
+    runs-on: ubuntu-latest
+    name: Build Prometheus Operator rule config map to rule file CRDs CLI tool
+    steps:
+    - uses: actions/checkout@v2
+    - uses: actions/setup-go@v2
+      with:
+        go-version: ${{ env.golang-version }}
+    - run: cd cmd/po-rule-migration && go install
+  unit-tests:
+    runs-on: ubuntu-latest
+    name: Unit tests
+    steps:
+    - uses: actions/checkout@v2
+    - uses: actions/setup-go@v2
+      with:
+        go-version: ${{ env.golang-version }}
+    - run: make test-unit
+  extended-tests:
+    runs-on: ubuntu-latest
+    name: Extended tests
+    steps:
+    - uses: actions/checkout@v2
+    - uses: actions/setup-go@v2
+      with:
+        go-version: ${{ env.golang-version }}
+    - run: make test-long
+  e2e-tests:
+    name: E2E tests
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        suite: [alertmanager, prometheus, thanosruler]
+        include:
+          - suite: alertmanager
+            prometheus: "exclude"
+            alertmanager: ""
+            thanosruler: "exclude"
+          - suite: prometheus
+            prometheus: ""
+            alertmanager: "exclude"
+            thanosruler: "exclude"
+          - suite: thanosruler
+            prometheus: "exclude"
+            alertmanager: "exclude"
+            thanosruler: ""
+    steps:
+    - uses: actions/checkout@v2
+    - name: Start KinD
+      uses: engineerd/setup-kind@v0.4.0
+      with:
+        version: ${{ env.kind-version }}
+        image: ${{ env.kind-image }}
+    - name: Check cluster
+      run: |
+        kubectl cluster-info
+        kubectl get pods -A
+    - name: Build and load images
+      run: |
+        export SHELL=/bin/bash
+        make build image
+        kind load docker-image quay.io/prometheus-operator/prometheus-operator:$(git rev-parse --short HEAD)
+        kind load docker-image quay.io/prometheus-operator/prometheus-config-reloader:$(git rev-parse --short HEAD)
+        kubectl apply -f scripts/minikube-rbac.yaml
+    - name: Run tests
+      run: >
+        EXCLUDE_ALERTMANAGER_TESTS=${{ matrix.alertmanager }}
+        EXCLUDE_PROMETHEUS_TESTS=${{ matrix.prometheus }}
+        EXCLUDE_THANOS_TESTS=${{ matrix.thanosruler }}
+        make test-e2e
+  publish:
+    runs-on: ubuntu-latest
+    name: Publish container images to quay.io
+    if: github.event_name == 'push'
+    needs:
+    - generate
+    - build
+    - po-rule-migration
+    - unit-tests
+    - extended-tests
+    - e2e-tests
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+      - name: Login to Quay.io
+        uses: docker/login-action@v1
+        with:
+          registry: quay.io
+          username: ${{ secrets.QUAY_USERNAME }}
+          password: ${{ secrets.QUAY_PASSWORD }}
+      - name: Build images and push
+        run: ./scripts/push-docker-image.sh
diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml
deleted file mode 100644
index e13ab0f96..000000000
--- a/.github/workflows/e2e.yaml
+++ /dev/null
@@ -1,77 +0,0 @@
-name: e2e
-on:
-  - push
-  - pull_request
-jobs:
-  prometheus:
-    runs-on: ubuntu-latest
-    name: prometheus tests
-    steps:
-    - uses: actions/checkout@v2
-    - name: Start KinD
-      uses: engineerd/setup-kind@v0.4.0
-      with:
-        version: v0.8.1
-        image: kindest/node:v1.18.8
-    - name: Check cluster
-      run: |
-        kubectl cluster-info
-        kubectl get pods -A
-    - name: Build and load images
-      run: |
-        export SHELL=/bin/bash
-        make build image
-        kind load docker-image quay.io/prometheus-operator/prometheus-operator:$(git rev-parse --short HEAD)
-        kind load docker-image quay.io/prometheus-operator/prometheus-config-reloader:$(git rev-parse --short HEAD)
-        kubectl apply -f scripts/minikube-rbac.yaml
-    - name: Run tests
-      run:
-        EXCLUDE_ALERTMANAGER_TESTS=true EXCLUDE_THANOS_TESTS=true make test-e2e
-  alertmanager:
-    runs-on: ubuntu-latest
-    name: alertmanager tests
-    steps:
-    - uses: actions/checkout@v2
-    - name: Start KinD
-      uses: engineerd/setup-kind@v0.4.0
-      with:
-        version: v0.8.1
-        image: kindest/node:v1.18.8
-    - name: Check cluster
-      run: |
-        kubectl cluster-info
-        kubectl get pods -A
-    - name: Build and load images
-      run: |
-        export SHELL=/bin/bash
-        make build image
-        kind load docker-image quay.io/prometheus-operator/prometheus-operator:$(git rev-parse --short HEAD)
-        kind load docker-image quay.io/prometheus-operator/prometheus-config-reloader:$(git rev-parse --short HEAD)
-        kubectl apply -f scripts/minikube-rbac.yaml
-    - name: Run tests
-      run:
-        EXCLUDE_PROMETHEUS_TESTS=true EXCLUDE_THANOS_TESTS=true make test-e2e
-  thanosruler:
-    runs-on: ubuntu-latest
-    name: thanos ruler tests
-    steps:
-    - uses: actions/checkout@v2
-    - name: Start KinD
-      uses: engineerd/setup-kind@v0.4.0
-      with:
-        version: v0.8.1
-        image: kindest/node:v1.18.8
-    - name: Check cluster
-      run: |
-        kubectl cluster-info
-        kubectl get pods -A
-    - name: Build and load images
-      run: |
-        export SHELL=/bin/bash
-        make build image
-        kind load docker-image quay.io/prometheus-operator/prometheus-operator:$(git rev-parse --short HEAD)
-        kind load docker-image quay.io/prometheus-operator/prometheus-config-reloader:$(git rev-parse --short HEAD)
-        kubectl apply -f scripts/minikube-rbac.yaml
-    - name: Run tests
-      run:
-        EXCLUDE_ALERTMANAGER_TESTS=true EXCLUDE_PROMETHEUS_TESTS=true make test-e2e
diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
deleted file mode 100644
index 59b29562a..000000000
--- a/.github/workflows/tests.yaml
+++ /dev/null
@@ -1,60 +0,0 @@
-name: tests
-on:
-  - push
-  - pull_request
-jobs:
-  generate:
-    runs-on: ${{ matrix.os }}
-    strategy:
-      matrix:
-        os:
-          - macos-latest
-          - ubuntu-latest
-    name: generate and format
-    steps:
-    - uses: actions/checkout@v2
-    - uses: actions/setup-go@v2
-      with:
-        go-version: '^1.14'
-    - run: make --always-make format generate && git diff --exit-code
-  build:
-    runs-on: ${{ matrix.os }}
-    strategy:
-      matrix:
-        os:
-          - macos-latest
-          - ubuntu-latest
-    name: build
-    steps:
-    - uses: actions/checkout@v2
-    - uses: actions/setup-go@v2
-      with:
-        go-version: '^1.14'
-    - run: make operator
-  po-rule-migration:
-    runs-on: ubuntu-latest
-    name: Build Prometheus Operator rule config map to rule file CRDs CLI tool
-    steps:
-    - uses: actions/checkout@v2
-    - uses: actions/setup-go@v2
-      with:
-        go-version: '^1.14'
-    - run: cd cmd/po-rule-migration && go install
-  unit-tests:
-    runs-on: ubuntu-latest
-    name: Unit tests
-    steps:
-    - uses: actions/checkout@v2
-    - uses: actions/setup-go@v2
-      with:
-        go-version: '^1.14'
-    - run: make test-unit
-  extended-tests:
-    runs-on: ubuntu-latest
-    name: Extended tests
-    steps:
-    - uses: actions/checkout@v2
-    - uses: actions/setup-go@v2
-      with:
-        go-version: '^1.14'
-    - run: make test-long
diff --git a/README.md b/README.md
index cb7bd592c..745fe4d47 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
 # Prometheus Operator
-[![Build Status](https://travis-ci.com/prometheus-operator/prometheus-operator.svg?branch=master)](https://travis-ci.com/prometheus-operator/prometheus-operator)
+[![Build Status](https://github.com/prometheus-operator/prometheus-operator/workflows/ci/badge.svg)](https://github.com/prometheus-operator/prometheus-operator/actions)
 [![Go Report Card](https://goreportcard.com/badge/prometheus-operator/prometheus-operator "Go Report Card")](https://goreportcard.com/report/prometheus-operator/prometheus-operator)
 [![Slack](https://img.shields.io/badge/join%20slack-%23prometheus--operator-brightgreen.svg)](http://slack.k8s.io/)
 
diff --git a/scripts/push-docker-image.sh b/scripts/push-docker-image.sh
new file mode 100755
index 000000000..7059449f6
--- /dev/null
+++ b/scripts/push-docker-image.sh
@@ -0,0 +1,45 @@
+#!/usr/bin/env bash
+# exit immediately when a command fails
+set -e
+# only exit with zero if all commands of the pipeline exit successfully
+set -o pipefail
+# error on unset variables
+set -u
+
+CPU_ARCHS="amd64 arm64 arm"
+
+# Push `-dev` images unless commit is tagged
+export REPO="${REPO:-"quay.io/prometheus-operator/prometheus-operator-dev"}"
+export REPO_PROMETHEUS_CONFIG_RELOADER="${REPO_PROMETHEUS_CONFIG_RELOADER:-"quay.io/prometheus-operator/prometheus-config-reloader-dev"}"
+if git describe --exact-match; then
+	export REPO="quay.io/prometheus-operator/prometheus-operator"
+	export REPO_PROMETHEUS_CONFIG_RELOADER="quay.io/prometheus-operator/prometheus-config-reloader"
+	export TAG="${GITHUB_REF#refs/tags/}"
+else
+	# Use branch name as dev image tags
+	TAG="${GITHUB_REF#refs/heads/}"
+fi
+
+for arch in ${CPU_ARCHS}; do
+	make --always-make image GOARCH="$arch" TAG="${TAG}-$arch"
+done
+
+export DOCKER_CLI_EXPERIMENTAL=enabled
+for r in ${REPO} ${REPO_PROMETHEUS_CONFIG_RELOADER}; do
+	# Images need to be on remote registry before creating manifests
+	for arch in $CPU_ARCHS; do
+		docker push "${r}:${TAG}-$arch"
+	done
+
+	# Create manifest to join all images under one virtual tag
+	docker manifest create -a "${r}:${TAG}" \
+				  "${r}:${TAG}-amd64" \
+				  "${r}:${TAG}-arm64" \
+				  "${r}:${TAG}-arm"
+
+	# Annotate to set which image is build for which CPU architecture
+	for arch in $CPU_ARCHS; do
+		docker manifest annotate --arch "$arch" "${r}:${TAG}" "${r}:${TAG}-$arch"
+	done
+	docker manifest push "${r}:${TAG}"
+done