diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 564c714f99cd85dd35c26160987cc2ae5ae2c5a2..3cca69827ae284f15ca0d68974551fb2c133d979 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -38,10 +38,6 @@ containers:
 .test-python: &test-python
   stage: initial
   image: python
-  before_script:
-    # this is required because pytables doesn't use a wheel on py37
-    - apt-get -yqq update
-    - apt-get -yqq install libhdf5-dev
   script:
     - python -m pip install .
     - python -m pip list installed
@@ -109,6 +105,15 @@ precommits-py3.9:
 #    CACHE_DIR: ".pip310"
 #    PYVERSION: "python310"
 
+install:
+  stage: initial
+  parallel:
+    matrix:
+      - EXTRA: [gw, mcmc, all]
+  image: containers.ligo.org/lscsoft/bilby/v2-bilby-python39
+  script:
+    - pip install .[$EXTRA]
+
 # ------------------- Test stage -------------------------------------------
 
 .unit-tests: &unit-test
diff --git a/MANIFEST.in b/MANIFEST.in
index 3f4cc5699cf7440351651f0b08cfc7e6ed11b071..d6ac4a64451d950e361c2c2c860632e80f8d4791 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,4 +1,8 @@
 include README.rst
 include LICENSE.md
 include requirements.txt
+include gw_requirements.txt
+include mcmc_requirements.txt
+include optional_requirements.txt
+include sampler_requirements.txt
 recursive-include test *.py *.prior
diff --git a/gw_requirements.txt b/gw_requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..9181f570314f79af35ce4c3fdbfc6ffdeec8cd48
--- /dev/null
+++ b/gw_requirements.txt
@@ -0,0 +1,5 @@
+astropy>=5
+lalsuite
+gwpy
+tables
+pyfftw
diff --git a/mcmc_requirements.txt b/mcmc_requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..6f5678c045b2d668402f1be6191c6b1622f8644e
--- /dev/null
+++ b/mcmc_requirements.txt
@@ -0,0 +1,2 @@
+scikit-learn
+nflows
diff --git a/optional_requirements.txt b/optional_requirements.txt
index d6ceb7188d00e64badeeb9ddf3d75e0ddcf87966..60e3fb4bac5cf62f9d4c92dcc277686efe4ac0d3 100644
--- a/optional_requirements.txt
+++ b/optional_requirements.txt
@@ -1,11 +1,3 @@
-astropy
 celerite
-lalsuite
 george
-gwpy
-theano
 plotly
-tables
-pyfftw
-scikit-learn
-nflows
diff --git a/requirements.txt b/requirements.txt
index e23d0a0cb1c11eaedf40c4390456148c84a7673e..70e457fcaca3e0d459bcf524f47300af8f1d9e93 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -8,6 +8,4 @@ pandas
 dill
 tqdm
 h5py
-tables
-astropy
-attrs
\ No newline at end of file
+attrs
diff --git a/setup.cfg b/setup.cfg
index 707f38591b65ef0ff2cc035583e48e290c94c131..e6195c56765a735c15e83303c549ea3af8635aad 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -16,11 +16,5 @@ addopts =
 name = bilby
 license_file = LICENSE.md
 
-[options.extras_require]
-gw =
-	astropy
-	gwpy
-	lalsuite
-
 [isort]
 known_third_party = astropy,attr,bilby,deepdish,gwin,gwinc,gwpy,lal,lalsimulation,matplotlib,mock,nflows,numpy,packaging,pandas,past,pycbc,pymc3,pytest,scipy,setuptools,skimage,torch
diff --git a/setup.py b/setup.py
index d195385b11ba6da20807d5acd55e7bc77952ef04..f937ab2bccf120d18ddc7e38b22260b70871a32c 100644
--- a/setup.py
+++ b/setup.py
@@ -56,8 +56,12 @@ def get_long_description():
     return long_description
 
 
-def get_requirements():
-    with open("requirements.txt", "r") as ff:
+def get_requirements(kind=None):
+    if kind is None:
+        fname = "requirements.txt"
+    else:
+        fname = f"{kind}_requirements.txt"
+    with open(fname, "r") as ff:
         requirements = ff.readlines()
     return requirements
 
@@ -107,6 +111,16 @@ setup(
     },
     python_requires=">=3.8",
     install_requires=get_requirements(),
+    extras_require={
+        "gw": get_requirements("gw"),
+        "mcmc": get_requirements("mcmc"),
+        "all": (
+            get_requirements("sampler")
+            + get_requirements("gw")
+            + get_requirements("mcmc")
+            + get_requirements("optional")
+        ),
+    },
     entry_points={
         "console_scripts": [
             "bilby_plot=cli_bilby.plot_multiple_posteriors:main",