diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 00000000..3059dda7
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,37 @@
+# Python bytecode
+__pycache__/
+*.py[cod]
+*$py.class
+
+# Virtual environments
+.venv/
+venv/
+env/
+ENV/
+
+# Packaging / build artifacts
+build/
+dist/
+*.egg-info/
+.eggs/
+pip-wheel-metadata/
+
+# Test / coverage / type-check caches
+.pytest_cache/
+.coverage
+.coverage.*
+.mypy_cache/
+.pyre/
+.ruff_cache/
+
+# Git / editor / OS files
+.git/
+.gitignore
+.gitattributes
+.vscode/
+.idea/
+.DS_Store
+Thumbs.db
+
+docker-compose*.yml
+Dockerfile*
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..218be9b1
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,45 @@
+# NOTE: This Docker image is for development purposes only.
+
+FROM python:3.12-slim-bookworm
+
+# System dependencies from the developer installation guide
+RUN apt-get update && \
+ apt-get install -y --no-install-recommends \
+ xmlsec1 \
+ gettext \
+ fping \
+ gdal-bin \
+ libproj-dev \
+ libgeos-dev \
+ libspatialite-dev \
+ spatialite-bin \
+ libsqlite3-mod-spatialite \
+ sqlite3 \
+ libsqlite3-dev \
+ zlib1g-dev \
+ libjpeg-dev \
+ openssl \
+ libssl-dev \
+ libglib2.0-0 \
+ libcairo2 \
+ libpango-1.0-0 \
+ libpangocairo-1.0-0 \
+ libgdk-pixbuf-2.0-0 \
+ shared-mime-info \
+ && rm -rf /var/lib/apt/lists/*
+
+# Install test requirements first so this layer is cached separately
+# from the source code copy below.
+COPY requirements-test.txt .
+RUN pip install --no-cache-dir -r requirements-test.txt
+
+# Copy source and install the package with all optional extras
+COPY . .
+RUN pip install --no-cache-dir -e ".[saml,openvpn_status]"
+
+ENV PYTHONUNBUFFERED=1 \
+ REDIS_HOST=redis \
+ INFLUXDB_HOST=influxdb
+
+EXPOSE 8000
+CMD ["bash", "docker-entrypoint.sh"]
diff --git a/docker-compose.yml b/docker-compose.yml
index a7592770..ee87f4f0 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,5 +1,3 @@
-version: "3"
-
services:
influxdb:
image: influxdb:1.8-alpine
@@ -11,12 +9,36 @@ services:
INFLUXDB_DB: openwisp2
INFLUXDB_USER: openwisp
INFLUXDB_USER_PASSWORD: openwisp
+ networks:
+ - openwisp
redis:
image: redis:alpine
ports:
- "6379:6379"
- entrypoint: redis-server --appendonly yes
+ command: redis-server --appendonly yes
+ networks:
+ - openwisp
+
+ radius:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ image: openwisp-radius-dev
+ working_dir: /opt/openwisp/tests
+ ports:
+ - "8000:8000"
+ volumes:
+ - .:/opt/openwisp/
+ depends_on:
+ - redis
+ - influxdb
+ networks:
+ - openwisp
volumes:
- influxdb-data: {}
+ influxdb-data:
+
+networks:
+ openwisp:
+ driver: bridge
diff --git a/docs/developer/installation.rst b/docs/developer/installation.rst
index f93e9b8e..3ae09ac8 100644
--- a/docs/developer/installation.rst
+++ b/docs/developer/installation.rst
@@ -115,6 +115,55 @@ Run quality assurance tests with:
./run-qa-checks
+.. _radius_dev_docker:
+
+Install and Run on Docker
+-------------------------
+
+.. warning::
+
+ This Docker image is for development purposes only.
+
+ For the official OpenWISP Docker images, see: :doc:`docker-openwisp
+ `.
+
+Ensure `Docker `_ and `Docker Compose
+`_ are installed on your system.
+
+Fork and clone the forked repository:
+
+.. code-block:: shell
+
+ git clone git://github.com//openwisp-radius
+
+Navigate into the cloned repository:
+
+.. code-block:: shell
+
+ cd openwisp-radius/
+
+Build the development image:
+
+.. code-block:: shell
+
+ docker compose build
+
+Start all services (Redis, InfluxDB, and the development server):
+
+.. code-block:: shell
+
+ docker compose up
+
+The development server will be available at ``http://127.0.0.1:8000/``.
+The admin interface is at ``http://127.0.0.1:8000/admin/`` with
+credentials ``admin`` / ``admin``.
+
+To run the test suite inside the container:
+
+.. code-block:: shell
+
+ docker compose run --rm radius bash -c "cd /opt/openwisp && ./runtests"
+
Alternative Sources
-------------------
diff --git a/tests/docker-entrypoint.sh b/tests/docker-entrypoint.sh
new file mode 100644
index 00000000..b7adcede
--- /dev/null
+++ b/tests/docker-entrypoint.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+set -euo pipefail
+
+create_superuser() {
+ local username="$1"
+ local email="$2"
+ local password="$3"
+ cat <