diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..f5a8323
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,10 @@
+version: 2
+updates:
+  - package-ecosystem: pip
+    directory: /
+    schedule:
+      interval: monthly
+  - package-ecosystem: github-actions
+    directory: /
+    schedule:
+      interval: monthly
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..7657c07
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,31 @@
+name: Lint and test
+
+on:
+  push:
+    branches: [main]
+  pull_request:
+    branches: ["**"]
+
+jobs:
+  test:
+    name: Test
+    runs-on: ubuntu-22.04
+    strategy:
+      matrix:
+        python-version: ["3.10", "3.11", "3.12"]
+
+    steps:
+      - uses: actions/checkout@v4
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python@v5
+        with:
+          python-version: ${{ matrix.python-version }}
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install -r requirements.txt
+          pip install -r requirements-test.txt
+      - name: Check formatting
+        run: ruff format --diff .
+      - name: Lint with ruff
+        run: ruff check --target-version=py310 .
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b694934
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.venv
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index c3130c6..2155fcd 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,7 +1,8 @@
 FROM python:3.12-bookworm
 
+COPY requirements.txt /requirements.txt
 COPY entrypoint.py /entrypoint.py
 
-RUN pip install py-allspice~=3.0
+RUN pip install -r /requirements.txt
 
 ENTRYPOINT [ "/entrypoint.py" ]
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..49bd2a8
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,8 @@
+[tool.ruff]
+lint.ignore = [
+  "E501", # Line length.
+  "F405", # Unknown identifier usage due to import *.
+]
+line-length = 100
+lint.select = ["E", "F", "RUF"]
+exclude = []
diff --git a/requirements-test.txt b/requirements-test.txt
new file mode 100644
index 0000000..d086415
--- /dev/null
+++ b/requirements-test.txt
@@ -0,0 +1 @@
+ruff==0.4.4
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..4a403e7
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1 @@
+py-allspice==3.0.0