diff --git a/.github/workflows/automatic_versioning.yml b/.github/workflows/automatic_versioning.yml
new file mode 100644
index 00000000..e9daaa45
--- /dev/null
+++ b/.github/workflows/automatic_versioning.yml
@@ -0,0 +1,126 @@
+# documentation: https://help.github.com/en/articles/workflow-syntax-for-github-actions
+name: Test version for tarball without git metadata
+on: [push, pull_request, workflow_dispatch]
+permissions: read-all
+jobs:
+  test_versioning_from_tarball:
+    # ubuntu <= 20.04 is required for python 3.6
+    runs-on: ubuntu-20.04
+    strategy:
+      fail-fast: false
+      matrix:
+        python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11']
+    steps:
+#         - name: Check out repository
+#           uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+#           with:
+#             persist-credentials: false
+#             fetch-depth: 0
+
+        - name: Set up Python
+          uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1  # v4.7.0
+          with:
+            python-version: ${{ matrix.python-version }}
+
+        - name: Install setuptools
+          run: |
+            if [[ "${{ matrix.python-version }}" == "3.6" ]]; then
+              # system installed setuptools version in RHEL8 and CO7
+              python -m pip install --user setuptools==39.2.0
+            fi
+
+        - name: Install setuptools_scm
+          run: |
+            if [[ "${{ matrix.python-version }}" == "3.6" ]]; then
+              python -m pip install --user 'setuptools_scm>=4.0.0,<=4.1.2'
+            else
+              python -m pip install --user setuptools_scm
+            fi
+
+        - name: Check python and setuptools versions
+          run: |
+            python --version
+            python -m pip --version
+            python -c 'import setuptools; print("setuptools", setuptools.__version__)'
+            python -m pip show setuptools_scm | grep Version
+
+        - name: Download and extract tarball for current commit
+          run: |
+            wget "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/archive/$GITHUB_SHA.tar.gz"
+            tar -xzf "$GITHUB_SHA.tar.gz"
+            # Check current directory contents
+            find .
+
+        - name: Check version when running against uninstalled git clone
+          run: |
+            echo "importing eessi.testsuite from:"
+            original_pythonpath=$PYTHONPATH
+            export PYTHONPATH="$PWD/test-suite-$GITHUB_SHA:$PYTHONPATH"
+            echo "PYTHONPATH: $PYTHONPATH"
+            python3 -c "import eessi.testsuite; print(eessi.testsuite.__file__)"
+
+            uninstalled_version=$(python3 -c "import eessi.testsuite; print(eessi.testsuite.__version__)")
+            echo "Version from uninstalled git clone: $uninstalled_version"
+            fallback_version=$(grep -oP 'fallback_version\s*=\s*"\K[^"]+' "test-suite-$GITHUB_SHA/pyproject.toml")
+
+            echo "Testing if this version is the fallback version from pyproject.toml ..."
+            if [[ "$uninstalled_version" != "$fallback_version" ]]; then
+                echo "Version $uninstalled_version not equal to $fallback_version"
+                exit 1
+            else
+                echo "... yes!"
+            fi
+
+            export PYTHONPATH="$original_pythonpath"
+
+        - name: Install from extracted tarball
+          run: |
+            # Make sure we get the fallback version from the pyprject.toml before changing workdir
+            fallback_version=$(grep -oP 'fallback_version\s*=\s*"\K[^"]+' "test-suite-$GITHUB_SHA/pyproject.toml")
+
+            # Make it easier to figure out CI issues in case of CI failures related to SCM versioning
+            export SETUPTOOLS_SCM_DEBUG=1
+
+            # Change dir to the extracted tarball
+            cd "test-suite-$GITHUB_SHA"
+
+            python -m pip install . --user
+
+            echo "Checking contents of .local"
+            find $HOME/.local
+
+            # make sure we are not in the source directory
+            cd $HOME
+
+            echo "Checking if file 'eessi/testsuite/_version.py' was generated by setuptools_scm":
+            cat $HOME/.local/lib/python${{ matrix.python-version}}/site-packages/eessi/testsuite/_version.py
+
+            echo "Checking if version can be imported directly from the version file"
+            if [[ "${{ matrix.python-version }}" == "3.6" ]]; then
+                versionfile_version=$(python -c 'from eessi.testsuite._version import version; print(version)')
+            else
+                versionfile_version=$(python -c 'from eessi.testsuite._version import __version__; print(__version__)')
+            fi
+            echo "Version from version file: $versionfile_version"
+
+            echo "Checking if we can import the __version__ from eessi.testsuite"
+            installed_version=$(python -c 'import eessi.testsuite; print(eessi.testsuite.__version__)')
+            echo "Version from installed testsuite: $installed_version"
+
+            # Read the fallback version from the pyproject.toml
+            echo "Testing if this is the fallback version from pyproject.toml ..."
+            if [[ "$installed_version" != "$fallback_version" ]]; then
+                echo "Version $installed_version not equal to $fallback_version"
+                exit 1
+            else
+                echo "... yes!"
+            fi
+
+            echo "Checking if the version imported from eessi.testsuite matches that from the version file ..."
+            if [[ "$versionfile_version" != "$installed_version" ]]; then
+                echo "Version $versionfile_version not equal to $installed_version"
+                exit 1
+            else
+                echo "... yes!"
+            fi
+
diff --git a/.github/workflows/check_versions.yml b/.github/workflows/check_versions.yml
new file mode 100644
index 00000000..c09dd07b
--- /dev/null
+++ b/.github/workflows/check_versions.yml
@@ -0,0 +1,47 @@
+# documentation: https://help.github.com/en/articles/workflow-syntax-for-github-actions
+name: Test fallback_version and version in run_reframe.sh against tags
+on: [push, pull_request, workflow_dispatch]
+permissions: read-all
+jobs:
+  test_fallback_version_against_tags:
+    # ubuntu <= 20.04 is required for python 3.6
+    runs-on: ubuntu-20.04
+    strategy:
+      fail-fast: false
+    steps:
+        - name: Check out repository
+          uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+          with:
+            persist-credentials: false
+            fetch-depth: 0
+
+        - name: Check fallback version and version used in run_reframe.sh
+          run: |
+            # Get fallback version
+            fallback_version=$(grep -oP 'fallback_version\s*=\s*"\K[^"]+' "pyproject.toml")
+            # Prepend fallback version with 'v', as that is also the case for the other two version strings
+            fallback_version="v$fallback_version"
+
+            # Get version from run_reframe.sh
+            run_reframe_testsuite_version=$(grep -oP 'EESSI_TESTSUITE_BRANCH\s*=\s*[^v]*\K[^"\x27]*' "CI/run_reframe.sh")
+
+            # Grab the tag for the highest version, by sorting by (semantic) version, and then filtering on patterns
+            # that match a pattern like v0.1.2. Finally, we grab the last to get the highest version
+            most_recent_version=$(git tag --sort=version:refname | grep -P "v[0-9]+\.[0-9]+\.[0-9]+" | tail -n 1)
+
+            echo "Testing if fallback version and EESSI_TESTSUITE_BRANCH version in CI/run_reframe.sh are the same"
+            if [[ "$fallback_version" != "$run_reframe_testsuite_version" ]]; then
+                echo "Version $fallback_version not equal to $run_reframe_testsuite_version"
+                exit 1
+            else
+                echo "... yes!"
+            fi
+
+            echo "Testing if fallback version and most recent version tag are the same"
+            if [[ "$fallback_version" != "$most_recent_version" ]]; then
+                echo "Version $fallback_version not equal to $most_recent_version"
+                exit 1
+            else
+                echo "... yes!"
+            fi
+
diff --git a/.github/workflows/pip_install.yml b/.github/workflows/pip_install.yml
index 5e8f2d9d..9a24dfe6 100644
--- a/.github/workflows/pip_install.yml
+++ b/.github/workflows/pip_install.yml
@@ -15,6 +15,7 @@ jobs:
           uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
           with:
             persist-credentials: false
+            fetch-depth: 0
 
         - name: Set up Python
           uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1  # v4.7.0
@@ -34,12 +35,18 @@ jobs:
 
         - name: Install EESSI test suite with 'pip install'
           run: |
+            # Make it easier to figure out CI issues in case of CI failures related to SCM versioning
+            export SETUPTOOLS_SCM_DEBUG=1
             # install from source distribution tarball, to test release as published on PyPI
             rm -rf dist
+            echo "Running python setup.py sdist"
             python setup.py sdist
             ls dist
 
+            echo "Running python -m pip install --user dist/eessi*.tar.gz"
             python -m pip install --user dist/eessi*.tar.gz
+
+            echo "Checking contents of .local"
             find $HOME/.local
 
             # make sure we are not in the source directory
@@ -49,5 +56,84 @@ jobs:
             python -m pip --version
             python -c 'import setuptools; print("setuptools", setuptools.__version__)'
 
+            echo "Checking if file 'eessi/testsuite/_version.py' was generated by setuptools_scm":
+            cat $HOME/.local/lib/python${{ matrix.python-version}}/site-packages/eessi/testsuite/_version.py
+
+            echo "Checking if version can be imported directly from the version file"
+            if [[ "${{ matrix.python-version }}" == "3.6" ]]; then
+                versionfile_version=$(python -c 'from eessi.testsuite._version import version; print(version)')
+            else
+                versionfile_version=$(python -c 'from eessi.testsuite._version import __version__; print(__version__)')
+            fi
+            echo "Version from version file: $versionfile_version"
+
+            echo "Checking if we can import the __version__ from eessi.testsuite"
+            testsuite_version=$(python -c 'import eessi.testsuite; print(eessi.testsuite.__version__)')
+            echo "Version imported from eessi.testsuite: $testsuite_version"
+
+            echo "Checking if the version imported from eessi.testsuite matches that from the version file ..."
+            if [[ "$versionfile_version" != "$testsuite_version" ]]; then
+                echo "Version $versionfile_version not equal to $testsuite_version"
+                exit 1
+            else
+                echo "... yes!"
+            fi
+
+            echo "Checking if we can import eessi.testsuite.utils"
+            python -c 'import eessi.testsuite.utils'
+
+            echo "Checking if we can import eessi.testsuite.tests.apps"
+            python -c 'import eessi.testsuite.tests.apps'
+
+
+        - name: Install EESSI test suite with 'pip install git+https'
+          run: |
+            # Get version from the installation in the previous step
+            testsuite_version=$(python -c 'import eessi.testsuite; print(eessi.testsuite.__version__)')
+
+            # Cleanup installation from previous step
+            echo "Uninstalling testsuite for next step"
+            python -m pip uninstall -y eessi-testsuite
+
+            pip install --user "git+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git@$GITHUB_SHA"
+
+            echo "Checking contents of .local"
+            find $HOME/.local
+
+            echo "Checking if file 'eessi/testsuite/_version.py' was generated by setuptools_scm":
+            cat $HOME/.local/lib/python${{ matrix.python-version}}/site-packages/eessi/testsuite/_version.py
+
+            echo "Checking if version can be imported directly from the version file"
+            if [[ "${{ matrix.python-version }}" == "3.6" ]]; then
+                githttps_versionfile_version=$(python -c 'from eessi.testsuite._version import version; print(version)')
+            else
+                githttps_versionfile_version=$(python -c 'from eessi.testsuite._version import __version__; print(__version__)')
+            fi
+            echo "Version from version file: $githttps_versionfile_version"
+
+            echo "Checking if we can import the __version__ from eessi.testsuite"
+            githttps_testsuite_version=$(python -c 'import eessi.testsuite; print(eessi.testsuite.__version__)')
+            echo "Version imported from eessi.testsuite: $githttps_testsuite_version"
+
+            echo "Checking if the version imported from eessi.testsuite matches that from the version file ..."
+            if [[ "$githttps_versionfile_version" != "$githttps_testsuite_version" ]]; then
+                echo "Version $githttps_versionfile_version not equal to $githttps_testsuite_version"
+                exit 1
+            else
+                echo "... yes!"
+            fi
+
+            echo "Checking if the version import from a regular pip install and the git+https based install are the same ..."
+            if [[ "$githttps_testsuite_version" != "$testsuite_version" ]]; then
+                echo "Version $githttps_testsuite_version not equal to $testsuite_version"
+                exit 1
+            else
+                echo "... yes!"
+            fi
+
+            echo "Checking if we can import eessi.testsuite.utils"
             python -c 'import eessi.testsuite.utils'
+
+            echo "Checking if we can import eessi.testsuite.tests.apps"
             python -c 'import eessi.testsuite.tests.apps'
+
diff --git a/eessi/testsuite/__init__.py b/eessi/testsuite/__init__.py
index e69de29b..c40994e4 100644
--- a/eessi/testsuite/__init__.py
+++ b/eessi/testsuite/__init__.py
@@ -0,0 +1,83 @@
+# WARNING: this file is imported in setup.py
+# To make sure this works, we should avoid using imports other than from the Python standard library
+
+try:
+    # If this is an installed package, setuptools_scm will have written the _version.py file in the current directory
+    from ._version import __version__
+except ImportError:
+    try:
+        # Setuptools_scm 4.1.2 (compatible with setuptools 39.2.0) write version instead of __version__
+        # This can be removed once we no longer care about python 3.6 with setuptools 39.2.0
+        from ._version import version
+        __version__ = version
+    except ImportError:
+        # Fallback for when the package is not installed, but git cloned. Note that this requires setuptools_scm to be
+        # available as a runtime dependency
+        # The advantage here is that it will generate development versions if not on a tagged release version
+        try:
+            from setuptools_scm import get_version
+            # Using a relative path for relative_to doesn't work, because it will be relative to the current working
+            # directory (which could be anywhere)
+            # __file__ is the location of this init file (a full path), and this gives us a predictable path to the root
+            # (namely: two levels up). Note that if we ever move this __init__ file relative to the root of the git
+            # tree, we'll need to adjust this
+            __version__ = get_version(root='../..', relative_to=__file__)
+        except (ImportError, LookupError):
+            # If running from a tarball (e.g. release tarball) downloaded from github, we will not have the .git
+            # folder available. Thus, setuptools_scm cannot determine the version in any way. Thus, use the
+            # fallback_version from the pyproject.toml file (which doesn't exist when this is installed as a package,
+            # but SHOULD exist when this is run from a downloaded tarball from git)
+
+            # Pyproject.toml should be two levels up from this file
+            import os.path
+            pyproject_toml = "%s/../../pyproject.toml" % os.path.dirname(__file__)
+
+            # Variables to track if we're in the right section and to store the fallback_version
+            in_setuptools_scm_section = False
+            fallback_version = None
+
+            file = None
+            try:
+                file = open(pyproject_toml, 'r')
+                # Open the file and parse it manually
+                fallback_version = None
+                with file:
+                    for line in file:
+                        stripped_line = line.strip()
+
+                        # Check if we're entering the [tool.setuptools_scm] section
+                        if stripped_line == "[tool.setuptools_scm]":
+                            in_setuptools_scm_section = True
+                        elif stripped_line.startswith("[") and in_setuptools_scm_section:
+                            # We've reached a new section, so stop searching
+                            break
+
+                        # If we're in the right section, look for the fallback_version key
+                        if in_setuptools_scm_section and stripped_line.startswith("fallback_version"):
+                            # Extract the value after the '=' sign and strip any surrounding quotes or whitespace
+                            fallback_version = stripped_line.split('=', 1)[1].strip().strip('"').strip("'")
+                            break
+                # Account for the possibility that we failed to extract the fallback_version field from pyproject.toml
+                if fallback_version:
+                    __version__ = fallback_version
+                else:
+                    msg = "fallback_version not found in file %s" % pyproject_toml
+                    msg += " when trying the get the EESSI test suite version. This should never happen."
+                    msg += " Please report an issue on Github, including information on how you installed"
+                    msg += " the EESSI test suite."
+                    print(msg)
+            except FileNotFoundError:
+                msg = "File %s not found when trying to extract the EESSI test suite version from" % pyproject_toml
+                msg += " pyproject.toml. This should never happen. Please report an issue on GitHub,"
+                msg += " including information on how you installed the EESSI test suite."
+                print(msg)
+            except Exception as e:
+                print("When trying to open file %s, an exception was raised: %s." % (pyproject_toml, e))
+
+# One of the above three methods to get __version__ defined SHOULD work in any situation.
+# It's considered a bug you reach this point without having a __version__ set
+if not __version__:
+    msg = "__version__ should have been defined by now, but it is not."
+    msg += " This is considered a bug, please report it in an issue on Github for the"
+    msg += " EESSI test suite."
+    raise ValueError(msg)
diff --git a/pyproject.toml b/pyproject.toml
index bcc958c6..c8af85a5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,10 +1,9 @@
 [build-system]
-requires = ["setuptools"]
+requires = ["setuptools>=42", "wheel"]
 build-backend = "setuptools.build_meta"
 
 [project]
 name = "eessi-testsuite"
-version = "0.4.0"
 description = "Test suite for the EESSI software stack"
 readme = "README.md"
 license = {file = "LICENSE"}
@@ -12,6 +11,7 @@ classifiers = [
   "Programming Language :: Python"
 ]
 requires-python = ">=3.6"
+dynamic = ["version"]
 
 [project.urls]
 "Homepage" = "https://eessi.io/docs/test-suite"
@@ -19,3 +19,9 @@ requires-python = ">=3.6"
 
 [tool.setuptools.packages.find]
 include = ["eessi*"]
+
+[tool.setuptools_scm]
+version_scheme = "guess-next-dev"
+local_scheme = "node-and-date"
+write_to = "eessi/testsuite/_version.py"
+fallback_version = "0.4.0"
diff --git a/setup.cfg b/setup.cfg
index 6c08d50c..3ef11c6e 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,5 @@
 [metadata]
 name = eessi-testsuite
-version = 0.4.0
 description = Test suite for the EESSI software stack
 long_description = file: README.md
 long_description_content_type = text/markdown
@@ -21,6 +20,10 @@ namespace_packages = eessi
 [options.packages.find]
 include = eessi*
 
+[setuptools_scm]
+version_scheme = guess-next-dev
+local_scheme = node-and-date
+
 [flake8]
 max-line-length = 120
 # ignore star imports (F403, F405)
diff --git a/setup.py b/setup.py
index a4f49f92..aff5ab6e 100644
--- a/setup.py
+++ b/setup.py
@@ -1,2 +1,42 @@
 import setuptools
-setuptools.setup()
+import sys
+import pkg_resources
+
+
+def get_version_by_import():
+    # Add the fallback version to whatever was set for scm_dict
+    sys.path.append('.')
+    from eessi.testsuite import __version__
+    return __version__
+
+
+# Get python version
+python_version = sys.version_info
+
+# Get setuptools version
+# We control it when installing from pyproject.toml, but not when installing from setup.py / setup.cfg
+# Check setuptools version
+current_setuptools_version = pkg_resources.parse_version(pkg_resources.get_distribution("setuptools").version)
+
+# Set the version requirement for setuptools_scm, depending on the combination of Python and setuptools version
+version_file_path = 'eessi/testsuite/_version.py'
+scm_dict = {'write_to': version_file_path}
+if python_version >= (3, 8) and current_setuptools_version >= pkg_resources.parse_version("61.0.0"):
+    setuptools_scm_requirement = 'setuptools_scm>8.0.0,<=8.1.0'
+    scm_dict = {'version_file': version_file_path}
+elif python_version >= (3, 7) and current_setuptools_version >= pkg_resources.parse_version("45.0.0"):
+    setuptools_scm_requirement = 'setuptools_scm>7.0.0,<=7.1.0'
+elif python_version >= (3, 6) and current_setuptools_version >= pkg_resources.parse_version("45.0.0"):
+    setuptools_scm_requirement = 'setuptools_scm>=6.0.0,<=6.4.2'
+elif python_version >= (3, 6) and current_setuptools_version >= pkg_resources.parse_version("42.0.0"):
+    setuptools_scm_requirement = 'setuptools_scm>=5.0.0,<=5.0.2'
+elif python_version >= (3, 6) and current_setuptools_version >= pkg_resources.parse_version("34.4.0"):
+    setuptools_scm_requirement = 'setuptools_scm>=4.0.0,<=4.1.2'
+
+# Set the fallback_version for scm based on what eessi.testsuite.__version__ returns
+scm_dict['fallback_version'] = get_version_by_import()
+
+setuptools.setup(
+    use_scm_version=scm_dict,
+    setup_requires=[setuptools_scm_requirement],
+)