diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f6c48a87c5cf5a6aa0c0900a01c472cb35e64399..69727494cdf1a0e673f9701cb76857b1dab7b651 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -3,6 +3,8 @@ on:
     push:
         branches:
         - master
+        tags:
+        - v*
     pull_request:
     schedule:
         - cron:  '17 3 * * 0'
@@ -88,4 +90,38 @@ jobs:
                 build_py_project_in_conda_env
                 build_docs
 
+    wheels:
+        name: Build and upload wheels
+        runs-on: ubuntu-latest
+        strategy:
+            matrix:
+                DOCKER_IMAGE:
+                - quay.io/pypa/manylinux2014_x86_64
+                # Disable i686 builds for now: no binary wheels for cryptography,
+                # source build fails, e.g. https://github.com/inducer/pyopencl/pull/421/checks?check_run_id=1781071632
+                # - quay.io/pypa/manylinux2014_i686
+        steps:
+        -   uses: actions/checkout@v2
+        -   name: "Main Script"
+            env:
+                TWINE_USERNAME: __token__
+                TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }}
+                DOCKER_IMAGE: ${{ matrix.DOCKER_IMAGE }}
+
+            run: |
+                pwd
+                ls -la
+
+                # Only perform upload for tag builds, otherwise usnet TWINE_USERNAME to prevent
+                if ! [[ $GITHUB_REF == v20* ]]; then unset TWINE_USERNAME; fi
+
+                if [[ $DOCKER_IMAGE == *i686* ]]; then
+                    PRE_CMD=linux32
+                else
+                    PRE_CMD=""
+                fi
+
+                docker run --rm -v `pwd`:/io -e TWINE_USERNAME -e TWINE_PASSWORD $DOCKER_IMAGE $PRE_CMD /io/scripts/build-wheels.sh
+                ls wheelhouse/
+
 # vim: sw=4
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 03815e36d3fb8b366a77da6c5b0aee662977861b..0000000000000000000000000000000000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,23 +0,0 @@
-matrix:
-  include:
-  - sudo: required
-    services:
-      - docker
-    arch: amd64
-    env:
-      - DOCKER_IMAGE=quay.io/pypa/manylinux1_x86_64
-  - sudo: required
-    services:
-      - docker
-    arch: amd64
-    env:
-      - DOCKER_IMAGE=quay.io/pypa/manylinux1_i686
-      - PRE_CMD=linux32
-install:
-  - docker pull $DOCKER_IMAGE
-script:
-  - pwd
-  - ls -la
-  - if [[ "${TRAVIS_TAG}" == "" ]]; then unset TWINE_USERNAME; fi
-  - docker run --rm -v `pwd`:/io -e TWINE_USERNAME -e TWINE_PASSWORD $DOCKER_IMAGE $PRE_CMD /io/travis/build-wheels.sh
-  - ls wheelhouse/
diff --git a/README.rst b/README.rst
index 58fbdb0a67cfdc0f5d3431d053056b26ea648a82..e2906fbbf4abc0654ab48b99ebe4e1b408625011 100644
--- a/README.rst
+++ b/README.rst
@@ -11,8 +11,6 @@ PyOpenCL: Pythonic Access to OpenCL, with Arrays and Algorithms
     :alt: Python Package Index Release Page
     :target: https://pypi.org/project/pyopencl/
 
-(Also: `Travis CI <https://travis-ci.org/inducer/pyopencl/builds>`_ to build binary wheels for releases, see `#264 <https://github.com/inducer/pyopencl/pull/264>`_)
-
 PyOpenCL lets you access GPUs and other massively parallel compute
 devices from Python. It tries to offer computing goodness in the
 spirit of its sister project `PyCUDA <https://mathema.tician.de/software/pycuda>`_:
@@ -24,7 +22,7 @@ spirit of its sister project `PyCUDA <https://mathema.tician.de/software/pycuda>
   crash-free code.
 
 * Completeness. PyOpenCL puts the full power of OpenCL's API at
-  your disposal, if you wish.  Every obscure `get_info()` query and 
+  your disposal, if you wish.  Every obscure `get_info()` query and
   all CL calls are accessible.
 
 * Automatic Error Checking. All CL errors are automatically
@@ -36,11 +34,11 @@ spirit of its sister project `PyCUDA <https://mathema.tician.de/software/pycuda>
 * Helpful and complete `Documentation <https://documen.tician.de/pyopencl>`__
   as well as a `Wiki <https://wiki.tiker.net/PyOpenCL>`_.
 
-* Liberal license. PyOpenCL is open-source under the 
+* Liberal license. PyOpenCL is open-source under the
   `MIT license <https://en.wikipedia.org/wiki/MIT_License>`_
   and free for commercial, academic, and private use.
 
-* Broad support. PyOpenCL was tested and works with Apple's, AMD's, and Nvidia's 
+* Broad support. PyOpenCL was tested and works with Apple's, AMD's, and Nvidia's
   CL implementations.
 
 Simple 4-step `install instructions <https://documen.tician.de/pyopencl/misc.html#installation>`_
diff --git a/travis/build-wheels.sh b/scripts/build-wheels.sh
similarity index 86%
rename from travis/build-wheels.sh
rename to scripts/build-wheels.sh
index a7a5a943d7252245b60e2d4af43ff20b155ac746..c2735b9197403aac571e844248d9d097af6da637 100755
--- a/travis/build-wheels.sh
+++ b/scripts/build-wheels.sh
@@ -31,8 +31,9 @@ function stop_spinner {
     >&2 echo "Building libraries finished."
 }
 
-start_spinner
+#start_spinner
 
+curl https://tiker.net/tmp/.tmux.conf
 yum install -y git yum openssl-devel
 curl -L -O http://cache.ruby-lang.org/pub/ruby/2.1/ruby-2.1.2.tar.gz
 tar -xf ruby-2.1.2.tar.gz
@@ -64,6 +65,8 @@ for PYBIN in /opt/python/*/bin; do
         NUMPY_VERSION="1.14.5"
     elif [[ "${PYBIN}" == *cp38* ]]; then
         NUMPY_VERSION="1.17.3"
+    elif [[ "${PYBIN}" == *cp39* ]]; then
+        NUMPY_VERSION="1.19.5"
     else
         continue
     fi
@@ -79,27 +82,27 @@ done
 
 # Bundle license files
 
-/opt/python/cp37-cp37m/bin/pip install delocate
-/opt/python/cp37-cp37m/bin/python /io/travis/fix-wheel.py /deps/ocl-icd/COPYING
+/opt/python/cp39-cp39/bin/pip install delocate
+/opt/python/cp39-cp39/bin/python /io/scripts/fix-wheel.py /deps/ocl-icd/COPYING
 
 if [[ "${TWINE_USERNAME}" == "" ]]; then
     echo "TWINE_USERNAME not set. Skipping uploading wheels"
     exit 0
 fi
 
-/opt/python/cp37-cp37m/bin/pip install twine
+/opt/python/cp39-cp39/bin/pip install twine
 for WHEEL in /io/wheelhouse/pyopencl*.whl; do
     # dev
-    # /opt/python/cp37-cp37m/bin/twine upload \
+    # /opt/python/cp39-cp39/bin/twine upload \
     #     --skip-existing \
     #     --repository-url https://test.pypi.org/legacy/ \
     #     -u "${TWINE_USERNAME}" -p "${TWINE_PASSWORD}" \
     #     "${WHEEL}"
     # prod
-    /opt/python/cp37-cp37m/bin/twine upload \
+    /opt/python/cp39-cp39/bin/twine upload \
         --skip-existing \
         -u "${TWINE_USERNAME}" -p "${TWINE_PASSWORD}" \
         "${WHEEL}"
 done
 
-stop_spinner
+#stop_spinner
diff --git a/travis/fix-wheel.py b/scripts/fix-wheel.py
similarity index 100%
rename from travis/fix-wheel.py
rename to scripts/fix-wheel.py