diff --git a/README.md b/README.md
index 7369e99..e7f60a4 100644
--- a/README.md
+++ b/README.md
@@ -7,4 +7,5 @@ This repository contains examples of deploying the [Particular Service Platform]
- [Deploying to Azure Container Apps using Bicep](/azure-container-apps/)
- [Running locally with Docker Compose](/docker-compose/)
- [Deploying to a Kubernetes cluster using helm](/helm/)
+- [Running locally with Terraform](/terraform/)
- [Deploying to Unraid OS](/unraid-templates/). These templates are also available directly within Unraid via its Community Apps section.
diff --git a/terraform/.gitignore b/terraform/.gitignore
new file mode 100644
index 0000000..f15a277
--- /dev/null
+++ b/terraform/.gitignore
@@ -0,0 +1,25 @@
+# Terraform state files
+*.tfstate
+*.tfstate.*
+
+# Crash log files
+crash.log
+crash.*.log
+
+# Exclude all .tfvars files, which might contain sensitive data
+*.tfvars
+*.tfvars.json
+
+# Ignore override files as they are usually used to override resources locally
+override.tf
+override.tf.json
+*_override.tf
+*_override.tf.json
+
+# Ignore CLI configuration files
+.terraformrc
+terraform.rc
+
+# Terraform directory
+.terraform/
+.terraform.lock.hcl
diff --git a/terraform/README.md b/terraform/README.md
new file mode 100644
index 0000000..4a3eb71
--- /dev/null
+++ b/terraform/README.md
@@ -0,0 +1,150 @@
+# Running with Terraform
+
+This [Terraform](https://www.terraform.io/) configuration provides an example of spinning up [ServiceControl](https://docs.particular.net/servicecontrol/) and [ServicePulse](https://docs.particular.net/servicepulse/) in a local development environment, using a container-hosted [RabbitMQ](https://docs.particular.net/transports/rabbitmq/) broker as the message transport.
+
+Running ServiceControl and ServicePulse locally in containers provides a way to use and test Service Platform features during local development on any platform, without needing to install Windows services.
+
+## Prerequisites
+
+1. [Docker](https://docs.docker.com/get-docker/) installed and running
+2. [Terraform](https://www.terraform.io/downloads) (version 1.0 or later) installed
+
+## Usage
+
+### Initialize Terraform
+
+Before running Terraform for the first time, initialize the working directory to download the required provider plugins:
+
+```shell
+terraform init
+```
+
+### Configure variables (optional)
+
+You can customize the deployment by creating a `terraform.tfvars` file. An example file is provided:
+
+```shell
+cp terraform.tfvars.example terraform.tfvars
+# Edit terraform.tfvars with your preferred settings
+```
+
+Key variables you might want to customize:
+- `servicecontrol_tag`: Version tag for ServiceControl images (default: `latest`)
+- `servicepulse_tag`: Version tag for ServicePulse image (default: `latest`)
+- `transport_type`: Message transport type (default: `RabbitMQ.QuorumConventionalRouting`)
+- `connection_string`: Transport connection string
+- `particular_license`: Your Particular Software license (optional, uses trial if not provided)
+
+### Deploy the infrastructure
+
+To see what Terraform will create:
+
+```shell
+terraform plan
+```
+
+To deploy the containers:
+
+```shell
+terraform apply
+```
+
+Terraform will show you the planned changes and ask for confirmation. Type `yes` to proceed.
+
+### Access the services
+
+Once deployed, the following services will be available:
+
+* [ServicePulse](https://docs.particular.net/servicepulse/) at http://localhost:9090
+* [ServiceInsight](https://docs.particular.net/serviceinsight/) can connect to http://localhost:33333/api
+* RabbitMQ Management UI at http://localhost:15672 (credentials: `guest`/`guest`)
+
+To see all output URLs, run:
+
+```shell
+terraform output
+```
+
+### Destroy the infrastructure
+
+To stop and remove all containers and resources:
+
+```shell
+terraform destroy
+```
+
+Type `yes` when prompted to confirm the destruction.
+
+## Implementation details
+
+* The ports for all services are exposed to localhost:
+ * `33333`: ServiceControl API
+ * `44444`: Audit API
+ * `33633`: Monitoring API
+ * `8080`: RavenDB backend
+ * `9090`: ServicePulse UI
+ * `5672`: RabbitMQ AMQP
+ * `15672`: RabbitMQ Management UI
+* All containers are connected via a dedicated Docker network (`service-platform-network`)
+* Persistent Docker volumes are created for:
+ * RabbitMQ data
+ * RavenDB configuration
+ * RavenDB data
+* One instance of the [`servicecontrol-ravendb` container](https://docs.particular.net/servicecontrol/ravendb/containers) is used for both the [`servicecontrol`](https://docs.particular.net/servicecontrol/servicecontrol-instances/deployment/containers) and [`servicecontrol-audit`](https://docs.particular.net/servicecontrol/audit-instances/deployment/containers) containers.
+ * _A single database container should not be shared between multiple ServiceControl instances in production scenarios._
+* The configuration uses the [Terraform Docker provider](https://registry.terraform.io/providers/kreuzwerker/docker/latest/docs) to manage containers locally
+
+## Advanced configuration
+
+### Using a different transport
+
+To use a different message transport, modify the `transport_type` and `connection_string` variables in your `terraform.tfvars` file. See the [ServiceControl Transports documentation](https://docs.particular.net/servicecontrol/transports) for supported transport types and connection string formats.
+
+### Remote Docker daemon
+
+To deploy to a remote Docker daemon, set the `docker_host` variable:
+
+```hcl
+docker_host = "tcp://remote-host:2376"
+```
+
+### Custom port mappings
+
+All external ports can be customized via variables. For example, to change the ServicePulse port:
+
+```hcl
+servicepulse_port = 8080
+```
+
+## Troubleshooting
+
+### Viewing container logs
+
+To view logs for a specific container:
+
+```shell
+docker logs servicecontrol
+docker logs servicecontrol-audit
+docker logs servicecontrol-monitoring
+docker logs servicepulse
+docker logs rabbitmq
+docker logs servicecontrol-db
+```
+
+### Inspecting Terraform state
+
+To see the current state of resources:
+
+```shell
+terraform show
+```
+
+### Force recreation of a container
+
+If a container is misbehaving, you can force Terraform to recreate it:
+
+```shell
+terraform apply -replace=docker_container.servicecontrol
+```
+
+Replace `servicecontrol` with any other container name as needed.
diff --git a/terraform/main.tf b/terraform/main.tf
new file mode 100644
index 0000000..d48547d
--- /dev/null
+++ b/terraform/main.tf
@@ -0,0 +1,248 @@
+terraform {
+ required_version = ">= 1.0"
+ required_providers {
+ docker = {
+ source = "kreuzwerker/docker"
+ version = "~> 3.0"
+ }
+ }
+}
+
+provider "docker" {
+ host = var.docker_host
+}
+
+# Create a Docker network for the service platform
+resource "docker_network" "service_platform" {
+ name = "service-platform-network"
+}
+
+# Create volumes for persistent data
+resource "docker_volume" "rabbitmq_data" {
+ name = "service-platform-rabbitmq-data"
+}
+
+resource "docker_volume" "raven_config" {
+ name = "service-platform-raven-config"
+}
+
+resource "docker_volume" "raven_data" {
+ name = "service-platform-raven-data"
+}
+
+# RabbitMQ container
+resource "docker_image" "rabbitmq" {
+ name = "rabbitmq:3-management"
+}
+
+resource "docker_container" "rabbitmq" {
+ name = "rabbitmq"
+ image = docker_image.rabbitmq.image_id
+
+ networks_advanced {
+ name = docker_network.service_platform.name
+ }
+
+ ports {
+ internal = 5672
+ external = var.rabbitmq_port
+ }
+
+ ports {
+ internal = 15672
+ external = var.rabbitmq_management_port
+ }
+
+ volumes {
+ volume_name = docker_volume.rabbitmq_data.name
+ container_path = "/var/lib/rabbitmq"
+ }
+
+ restart = "unless-stopped"
+
+ healthcheck {
+ test = ["CMD", "rabbitmq-diagnostics", "check_port_connectivity"]
+ interval = "30s"
+ timeout = "10s"
+ retries = 3
+ }
+}
+
+# ServiceControl RavenDB container
+resource "docker_image" "servicecontrol_ravendb" {
+ name = "particular/servicecontrol-ravendb:${var.servicecontrol_tag}"
+}
+
+resource "docker_container" "servicecontrol_db" {
+ name = "servicecontrol-db"
+ image = docker_image.servicecontrol_ravendb.image_id
+
+ networks_advanced {
+ name = docker_network.service_platform.name
+ }
+
+ ports {
+ internal = 8080
+ external = var.ravendb_port
+ }
+
+ volumes {
+ volume_name = docker_volume.raven_config.name
+ container_path = "/var/lib/ravendb/config"
+ }
+
+ volumes {
+ volume_name = docker_volume.raven_data.name
+ container_path = "/var/lib/ravendb/data"
+ }
+
+ restart = "unless-stopped"
+
+ # RavenDB container includes a built-in healthcheck script
+ healthcheck {
+ test = ["CMD-SHELL", "/usr/lib/ravendb/scripts/healthcheck.sh"]
+ interval = "30s"
+ timeout = "10s"
+ retries = 3
+ }
+}
+
+# ServiceControl (Error instance) container
+resource "docker_image" "servicecontrol" {
+ name = "particular/servicecontrol:${var.servicecontrol_tag}"
+}
+
+resource "docker_container" "servicecontrol" {
+ name = "servicecontrol"
+ image = docker_image.servicecontrol.image_id
+
+ networks_advanced {
+ name = docker_network.service_platform.name
+ }
+
+ ports {
+ internal = 33333
+ external = var.servicecontrol_port
+ }
+
+ # Note: Environment variables use internal container ports (33333, 44444, 33633, 8080)
+ # which are fixed by the container images, not the external mapped ports.
+ env = [
+ "RAVENDB_CONNECTIONSTRING=http://servicecontrol-db:8080",
+ "REMOTEINSTANCES=[{\"api_uri\":\"http://servicecontrol-audit:44444/api\"}]",
+ "TRANSPORTTYPE=${var.transport_type}",
+ "CONNECTIONSTRING=${var.connection_string}",
+ "PARTICULARSOFTWARE_LICENSE=${var.particular_license}"
+ ]
+
+ command = ["--setup-and-run"]
+
+ restart = "unless-stopped"
+
+ depends_on = [
+ docker_container.servicecontrol_db,
+ docker_container.rabbitmq
+ ]
+}
+
+# ServiceControl Audit container
+resource "docker_image" "servicecontrol_audit" {
+ name = "particular/servicecontrol-audit:${var.servicecontrol_tag}"
+}
+
+resource "docker_container" "servicecontrol_audit" {
+ name = "servicecontrol-audit"
+ image = docker_image.servicecontrol_audit.image_id
+
+ networks_advanced {
+ name = docker_network.service_platform.name
+ }
+
+ ports {
+ internal = 44444
+ external = var.servicecontrol_audit_port
+ }
+
+ env = [
+ "RAVENDB_CONNECTIONSTRING=http://servicecontrol-db:8080",
+ "SERVICECONTROLQUEUEADDRESS=Particular.ServiceControl",
+ "TRANSPORTTYPE=${var.transport_type}",
+ "CONNECTIONSTRING=${var.connection_string}",
+ "PARTICULARSOFTWARE_LICENSE=${var.particular_license}"
+ ]
+
+ command = ["--setup-and-run"]
+
+ restart = "unless-stopped"
+
+ depends_on = [
+ docker_container.servicecontrol_db,
+ docker_container.rabbitmq
+ ]
+}
+
+# ServiceControl Monitoring container
+resource "docker_image" "servicecontrol_monitoring" {
+ name = "particular/servicecontrol-monitoring:${var.servicecontrol_tag}"
+}
+
+resource "docker_container" "servicecontrol_monitoring" {
+ name = "servicecontrol-monitoring"
+ image = docker_image.servicecontrol_monitoring.image_id
+
+ networks_advanced {
+ name = docker_network.service_platform.name
+ }
+
+ ports {
+ internal = 33633
+ external = var.servicecontrol_monitoring_port
+ }
+
+ env = [
+ "TRANSPORTTYPE=${var.transport_type}",
+ "CONNECTIONSTRING=${var.connection_string}",
+ "PARTICULARSOFTWARE_LICENSE=${var.particular_license}"
+ ]
+
+ command = ["--setup-and-run"]
+
+ restart = "unless-stopped"
+
+ depends_on = [
+ docker_container.rabbitmq
+ ]
+}
+
+# ServicePulse container
+resource "docker_image" "servicepulse" {
+ name = "particular/servicepulse:${var.servicepulse_tag}"
+}
+
+resource "docker_container" "servicepulse" {
+ name = "servicepulse"
+ image = docker_image.servicepulse.image_id
+
+ networks_advanced {
+ name = docker_network.service_platform.name
+ }
+
+ ports {
+ internal = 9090
+ external = var.servicepulse_port
+ }
+
+ # Note: URLs use internal container ports (33333, 33633), not external mapped ports
+ env = [
+ "SERVICECONTROL_URL=http://servicecontrol:33333",
+ "MONITORING_URL=http://servicecontrol-monitoring:33633"
+ ]
+
+ restart = "unless-stopped"
+
+ depends_on = [
+ docker_container.servicecontrol,
+ docker_container.servicecontrol_monitoring,
+ docker_container.rabbitmq
+ ]
+}
diff --git a/terraform/outputs.tf b/terraform/outputs.tf
new file mode 100644
index 0000000..2663d8d
--- /dev/null
+++ b/terraform/outputs.tf
@@ -0,0 +1,34 @@
+output "servicepulse_url" {
+ description = "URL for ServicePulse web interface"
+ value = "http://localhost:${var.servicepulse_port}"
+}
+
+output "servicecontrol_api_url" {
+ description = "URL for ServiceControl API (for use with ServiceInsight)"
+ value = "http://localhost:${var.servicecontrol_port}/api"
+}
+
+output "servicecontrol_audit_api_url" {
+ description = "URL for ServiceControl Audit API"
+ value = "http://localhost:${var.servicecontrol_audit_port}/api"
+}
+
+output "servicecontrol_monitoring_url" {
+ description = "URL for ServiceControl Monitoring API"
+ value = "http://localhost:${var.servicecontrol_monitoring_port}"
+}
+
+output "ravendb_url" {
+ description = "URL for RavenDB management interface"
+ value = "http://localhost:${var.ravendb_port}"
+}
+
+output "rabbitmq_management_url" {
+ description = "URL for RabbitMQ Management UI (credentials: guest/guest)"
+ value = "http://localhost:${var.rabbitmq_management_port}"
+}
+
+output "network_name" {
+ description = "Name of the Docker network created for the service platform"
+ value = docker_network.service_platform.name
+}
diff --git a/terraform/terraform.tfvars.example b/terraform/terraform.tfvars.example
new file mode 100644
index 0000000..247ee2b
--- /dev/null
+++ b/terraform/terraform.tfvars.example
@@ -0,0 +1,30 @@
+# Example Terraform variables file
+# Copy this file to terraform.tfvars and customize as needed
+
+# Docker host (defaults to local Docker daemon)
+# docker_host = "unix:///var/run/docker.sock"
+
+# Image tags
+servicecontrol_tag = "latest"
+servicepulse_tag = "latest"
+
+# Transport configuration
+transport_type = "RabbitMQ.QuorumConventionalRouting"
+connection_string = "host=rabbitmq;username=guest;password=guest"
+
+# Optional: Particular Software license
+# particular_license = <<-EOT
+#
+#
+# ...
+#
+# EOT
+
+# Port mappings (defaults shown)
+# servicecontrol_port = 33333
+# servicecontrol_audit_port = 44444
+# servicecontrol_monitoring_port = 33633
+# servicepulse_port = 9090
+# ravendb_port = 8080
+# rabbitmq_port = 5672
+# rabbitmq_management_port = 15672
diff --git a/terraform/variables.tf b/terraform/variables.tf
new file mode 100644
index 0000000..dc4293e
--- /dev/null
+++ b/terraform/variables.tf
@@ -0,0 +1,86 @@
+variable "docker_host" {
+ description = "Docker daemon host (e.g., unix:///var/run/docker.sock for local, or tcp://host:2376 for remote)"
+ type = string
+ default = "unix:///var/run/docker.sock"
+}
+
+variable "servicecontrol_tag" {
+ description = "Docker image tag for ServiceControl components"
+ type = string
+ default = "latest"
+}
+
+variable "servicepulse_tag" {
+ description = "Docker image tag for ServicePulse"
+ type = string
+ default = "latest"
+}
+
+variable "transport_type" {
+ description = "The message transport type (e.g., RabbitMQ.QuorumConventionalRouting)"
+ type = string
+ default = "RabbitMQ.QuorumConventionalRouting"
+}
+
+variable "connection_string" {
+ description = "Connection string for the message transport"
+ type = string
+ default = "host=rabbitmq;username=guest;password=guest"
+}
+
+variable "particular_license" {
+ description = <<-EOT
+ Particular Software license content (optional, will use trial if not provided).
+ Should contain the full XML content of the license file.
+ Example format:
+
+
+ ...
+
+ EOT
+ type = string
+ default = ""
+ sensitive = true
+}
+
+variable "servicecontrol_port" {
+ description = "External port for ServiceControl API"
+ type = number
+ default = 33333
+}
+
+variable "servicecontrol_audit_port" {
+ description = "External port for ServiceControl Audit API"
+ type = number
+ default = 44444
+}
+
+variable "servicecontrol_monitoring_port" {
+ description = "External port for ServiceControl Monitoring API"
+ type = number
+ default = 33633
+}
+
+variable "servicepulse_port" {
+ description = "External port for ServicePulse UI"
+ type = number
+ default = 9090
+}
+
+variable "ravendb_port" {
+ description = "External port for RavenDB"
+ type = number
+ default = 8080
+}
+
+variable "rabbitmq_port" {
+ description = "External port for RabbitMQ AMQP"
+ type = number
+ default = 5672
+}
+
+variable "rabbitmq_management_port" {
+ description = "External port for RabbitMQ Management UI"
+ type = number
+ default = 15672
+}