diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 000000000..627d921ea
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,14 @@
+.DS_Store
+.idea
+.vscode
+*.bak
+*.iml
+*.ipr
+*.swp
+*.tmp
+
+/.yardoc
+/bin
+/build
+/doc/api
+/doc/capi
diff --git a/.gitignore b/.gitignore
index c2918f31a..eb5b5ddfa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,3 +32,4 @@ compile_flags.txt
cscope.files
cscope.out
tags
+!Gemfile.lock
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a59463e77..d8eb6a8ea 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -54,6 +54,69 @@ We use [GitHub Actions](.github/workflows/lint.yml) to run `pre-commit` on every
- [pre-commit autoupdate](https://pre-commit.com/#pre-commit-autoupdate)
- [Temporarily disabling hooks](https://pre-commit.com/#temporarily-disabling-hooks)
+## Docker
+
+We have both a `Dockerfile` and `docker-compose.yml` files in the repository root.
+You can run these with the command line or use
+[Docker Desktop](https://www.docker.com/products/docker-desktop/).
+
+The Docker image is running Debian bullseye with Ruby and Python installed.
+You can build the Docker image with:
+
+`$ docker-compose build test`
+
+So far we just have one service: `test`. Running the default `docker-compose`
+command will create the Docker image, spin up a container and then build and
+run all mruby tests.
+
+The default `docker-compose` command is:
+
+`$ docker-compose -p mruby run test`
+
+You can also use Make or Rake to run the default `docker-compose`
+command from above:
+
+- `make composetest`
+- `rake composetest`
+
+List your Docker images with:
+
+```console
+$ docker images
+REPOSITORY TAG IMAGE ID CREATED SIZE
+mruby-test latest ec60f9536948 29 seconds ago 1.29GB
+```
+
+You can also run any custom `docker-compose` command which will override
+the default. For example to run `pre-commit run --all-files` type:
+
+`$ docker-compose -p mruby run test pre-commit run --all-files`
+
+For convenience, you can also run `pre-commit` with:
+
+- `make composecheck`
+- `rake composecheck`
+
+The bonus of running `pre-commit` with `docker-compose` is that you won't need
+to install `pre-commit` and the hooks on your local machine. And that also
+means you won't need to install `brew`, `conda` or `pip`.
+
+Note limitation: currently running `pre-commit` with `docker-compose` we
+skip the `check-executables-have-shebangs` hook.
+
+Two more examples of custom `docker-compose` commands are:
+
+- `$ docker-compose -p mruby run test ls`
+- `$ docker-compose -p mruby run test rake doc:api`
+
+If you want to test using a different `docker-compose` YAML config file you
+can use the `-f` flag:
+
+`$ docker-compose -p mruby -f docker-compose.test.yml run test`
+
+-
+-
+
## Spell Checking
We are using `pre-commit` to run [codespell](https://github.com/codespell-project/codespell)
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 000000000..f66995625
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,17 @@
+FROM ruby:3.2.2-bullseye
+
+RUN apt-get update && apt-get install --no-install-recommends -y python3-pip shellcheck \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/*
+
+WORKDIR /app
+
+COPY Gemfile .
+
+COPY Gemfile.lock .
+
+COPY .pre-commit-config.yaml .
+
+RUN bundle install && pip3 install pre-commit && git init . && pre-commit install-hooks
+
+COPY . .
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 000000000..497e42ffc
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+source 'https://rubygems.org'
+
+gem 'rake'
+gem 'yard'
+gem 'yard-coderay'
+gem 'yard-mruby'
diff --git a/Gemfile.lock b/Gemfile.lock
new file mode 100644
index 000000000..37e5d651f
--- /dev/null
+++ b/Gemfile.lock
@@ -0,0 +1,27 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ coderay (1.1.3)
+ rake (13.0.6)
+ webrick (1.7.0)
+ yard (0.9.28)
+ webrick (~> 1.7.0)
+ yard-coderay (0.1.0)
+ coderay
+ yard
+ yard-mruby (0.3.0)
+ yard (~> 0.9.0)
+
+PLATFORMS
+ ruby
+ x86_64-darwin-21
+ x86_64-linux
+
+DEPENDENCIES
+ rake
+ yard
+ yard-coderay
+ yard-mruby
+
+BUNDLED WITH
+ 2.4.10
diff --git a/Makefile b/Makefile
index b114c09b5..a62a126ea 100644
--- a/Makefile
+++ b/Makefile
@@ -25,3 +25,11 @@ checkinstall :
checkupdate :
pre-commit autoupdate
.PHONY : checkupdate
+
+composecheck :
+ docker-compose -p mruby run test pre-commit run --all-files
+.PHONY : composecheck
+
+composetest :
+ docker-compose -p mruby run test
+.PHONY : composetest
diff --git a/Rakefile b/Rakefile
index ef7d536c6..bd443b861 100644
--- a/Rakefile
+++ b/Rakefile
@@ -82,3 +82,13 @@ desc "check the pre-commit hooks for updates"
task :checkupdate do
sh "pre-commit autoupdate"
end
+
+desc "run all pre-commit hooks against all files with docker-compose"
+task :composecheck do
+ sh "docker-compose -p mruby run test pre-commit run --all-files"
+end
+
+desc "build and run all mruby tests with docker-compose"
+task :composetest do
+ sh "docker-compose -p mruby run test"
+end
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 000000000..223e85af2
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,13 @@
+version: '3'
+services:
+ test:
+ build:
+ context: .
+ command: sh -c 'rake deep_clean && rake -m test:build && rake test:run'
+ environment:
+ - MRUBY_CONFIG=ci/gcc-clang
+ - CC=gcc
+ - CXX=g++
+ - LD=gcc
+ - SKIP=check-executables-have-shebangs
+ working_dir: /app