diff --git a/.travis.yml b/.travis.yml
index 7406f31fe779c8656024db14f246f01fb8e59bd7..40081070d5ae3b4e50d886944bec68ff48c119e8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -24,6 +24,37 @@ jobs:
       script:
         - go run build/ci.go lint
 
+    # This builder does the Docker Hub build and upload for amd64
+    - stage: build
+      if: type = push
+      os: linux
+      dist: bionic
+      go: 1.16.x
+      env:
+        - docker
+      services:
+        - docker
+      git:
+        submodules: false # avoid cloning ethereum/tests
+      script:
+        - go run build/ci.go docker -upload karalabe/geth-docker-test
+
+    # This builder does the Docker Hub build and upload for arm64
+    - stage: build
+      if: type = push
+      os: linux
+      arch: arm64
+      dist: bionic
+      go: 1.16.x
+      env:
+        - docker
+      services:
+        - docker
+      git:
+        submodules: false # avoid cloning ethereum/tests
+      script:
+        - go run build/ci.go docker -upload karalabe/geth-docker-test
+
     # This builder does the Ubuntu PPA upload
     - stage: build
       if: type = push
diff --git a/build/ci.go b/build/ci.go
index 3752b1bed92b3ed05f5aacf041c37d0293f1276f..12e2b8b575d44928e5abb8596d3c200c03b2da06 100644
--- a/build/ci.go
+++ b/build/ci.go
@@ -182,6 +182,8 @@ func main() {
 		doLint(os.Args[2:])
 	case "archive":
 		doArchive(os.Args[2:])
+	case "docker":
+		doDockerImage(os.Args[2:])
 	case "debsrc":
 		doDebianSource(os.Args[2:])
 	case "nsis":
@@ -452,6 +454,56 @@ func maybeSkipArchive(env build.Environment) {
 	}
 }
 
+// Builds the docker images and optionally uploads them to Docker Hub.
+func doDockerImage(cmdline []string) {
+	var (
+		upload = flag.String("upload", "", `Where to upload the docker image (usually "ethereum/client-go")`)
+	)
+	flag.CommandLine.Parse(cmdline)
+
+	// Skip building and pushing docker images for PR builds
+	env := build.Env()
+	maybeSkipArchive(env)
+
+	// Retrieve the version infos to build and push to the following paths:
+	//  - ethereum/client-go:latest                            - Pushes to the master branch, Geth only
+	//  - ethereum/client-go:stable                            - Version tag publish on GitHub, Geth only
+	//  - ethereum/client-go:alltools-latest                   - Pushes to the master branch, Geth & tools
+	//  - ethereum/client-go:alltools-stable                   - Version tag publish on GitHub, Geth & tools
+	//  - ethereum/client-go:release-<major>.<minor>           - Version tag publish on GitHub, Geth only
+	//  - ethereum/client-go:alltools-release-<major>.<minor>  - Version tag publish on GitHub, Geth & tools
+	//  - ethereum/client-go:v<major>.<minor>.<patch>          - Version tag publish on GitHub, Geth only
+	//  - ethereum/client-go:alltools-v<major>.<minor>.<patch> - Version tag publish on GitHub, Geth & tools
+	var tags []string
+
+	switch {
+	case env.Branch == "master":
+		tags = []string{"latest"}
+	case strings.HasPrefix(env.Tag, "v1."):
+		tags = []string{"stable", fmt.Sprintf("release-1.%d", params.VersionMinor), params.Version}
+	}
+	// Build the docker images via CLI (don't pull in the `moby` dep to call 3 commands)
+	build.MustRunCommand("docker", "build", "--tag", fmt.Sprintf("%s:TAG", *upload), ".")
+	build.MustRunCommand("docker", "build", "--tag", fmt.Sprintf("%s:alltools-TAG", *upload), "-f", "Dockerfile.alltools", ".")
+
+	// Retrieve the upload credentials and authenticate
+	user := getenvBase64("DOCKER_HUB_USERNAME")
+	pass := getenvBase64("DOCKER_HUB_PASSWORD")
+
+	if len(user) > 0 && len(pass) > 0 {
+		auther := exec.Command("docker", "login", "-u", string(user), "--password-stdin")
+		auther.Stdin = bytes.NewReader(pass)
+		build.MustRun(auther)
+	}
+	// Tag and upload the images to Docker Hub
+	for _, tag := range tags {
+		build.MustRunCommand("docker", "image", "tag", fmt.Sprintf("%s:TAG", *upload), fmt.Sprintf("%s:%s", *upload, tag))
+		build.MustRunCommand("docker", "image", "tag", fmt.Sprintf("%s:alltools-TAG", *upload), fmt.Sprintf("%s:alltools-%s", *upload, tag))
+		build.MustRunCommand("docker", "push", fmt.Sprintf("%s:%s", *upload, tag))
+		build.MustRunCommand("docker", "push", fmt.Sprintf("%s:alltools-%s", *upload, tag))
+	}
+}
+
 // Debian Packaging
 func doDebianSource(cmdline []string) {
 	var (