diff --git a/modules/20-services-apps/n8n/.env.example b/modules/20-services-apps/n8n/.env.example index 18d8c6a..09aa183 100644 --- a/modules/20-services-apps/n8n/.env.example +++ b/modules/20-services-apps/n8n/.env.example @@ -8,3 +8,7 @@ N8N_PORT=5678 N8N_PROTOCOL=http WEBHOOK_URL=https://n8n.yourdomain.com/ NODE_FUNCTION_ALLOW_EXTERNAL=* + +# MCP +N8N_MCP_AUTH_TOKEN= +N8N_API_KEY= diff --git a/modules/20-services-apps/n8n/README.md b/modules/20-services-apps/n8n/README.md index 12f1144..1842c1d 100644 --- a/modules/20-services-apps/n8n/README.md +++ b/modules/20-services-apps/n8n/README.md @@ -1,17 +1,19 @@ # n8n Module -This module deploys [n8n](https://n8n.io/), a workflow automation tool for technical people, as Docker containers in the homelab environment. +This module deploys [n8n](https://n8n.io/), a workflow automation tool, along with its dependencies and the [n8n-mcp](https://github.com/czlonkowski/n8n-mcp) community node manager, as Docker containers in the homelab environment. ## Overview The n8n module: -- Deploys two Docker containers: +- Deploys four Docker containers: - `n8n`: The main workflow automation server - `n8n-postgres`: A PostgreSQL database backend + - `n8n-redis`: A Redis instance for queuing + - `n8n-mcp`: A community node management tool for n8n - Creates a dedicated Docker network (`n8n-network`) for container communication - Persists data to volumes on the host -- Provides service definition for integration with networking modules +- Provides service definitions for integration with networking modules ## Usage @@ -28,18 +30,21 @@ module "n8n" { | Variable | Description | Type | Default | | ------------- | ---------------------------------------------------------- | -------------- | ---------- | | `image_tag` | Tag of the n8n image to use | `string` | `"latest"` | -| `volume_path` | Host path for n8n and Postgres data volumes | `string` | - | +| `volume_path` | Host path for n8n, Postgres, Redis, and n8n-mcp data volumes | `string` | - | | `networks` | List of additional networks to which n8n should be attached | `list(string)` | `[]` | ## Outputs -| Output | Description | -| -------------------- | ---------------------------------------------------------- | -| `service_definition` | Service definition for integration with networking modules | +| Output | Description | +| ---------------------------- | ---------------------------------------------------------- | +| `service_definition` | Service definition for the n8n container | +| `n8n_mcp_service_definition` | Service definition for the n8n-mcp container | -## Service Definition +## Service Definitions -This module outputs a service definition that is used by the networking modules to expose the service. +This module outputs two service definitions that are used by the networking modules to expose the services. + +### n8n ```hcl { @@ -47,45 +52,63 @@ This module outputs a service definition that is used by the networking modules primary_port = 5678 endpoint = "http://n8n:5678" subdomains = ["n8n"] - publish_via = "tunnel" # Only publish through Cloudflare tunnel + publish_via = "tunnel" +} +``` + +### n8n-mcp + +```hcl +{ + name = "n8n-mcp" + primary_port = 3000 + endpoint = "http://n8n-mcp:3000" + subdomains = ["n8n-mcp"] + publish_via = "tunnel" } ``` ## Environment Variables -n8n requires several environment variables to function properly. These are stored in a `.env` file in the module directory and read using the `dotenv` Terraform provider: +The services require several environment variables to function properly. These are stored in a `.env` file in the module directory and read using the `dotenv` Terraform provider: -- Database configuration: +- **Database configuration (`n8n-postgres`)**: - `POSTGRES_USER`: Root PostgreSQL user - `POSTGRES_PASSWORD`: Root PostgreSQL password - `POSTGRES_DB`: Database name for n8n - `POSTGRES_NON_ROOT_USER`: Non-root user for n8n to connect with - `POSTGRES_NON_ROOT_PASSWORD`: Password for the non-root user -- n8n configuration: +- **n8n configuration (`n8n`)**: - `N8N_HOST`: Host for n8n to use - `N8N_PORT`: Port for n8n to use - `N8N_PROTOCOL`: Protocol for n8n (http or https) - `WEBHOOK_URL`: URL for webhooks - `NODE_FUNCTION_ALLOW_EXTERNAL`: Whether to allow external function calls +- **n8n-mcp configuration (`n8n-mcp`)**: + - `N8N_MCP_AUTH_TOKEN`: Authentication token for n8n-mcp. + - `N8N_API_KEY`: n8n API key for n8n-mcp to interact with the n8n instance. + ## Data Persistence -n8n stores its data in two main volumes: +The services store data in several volumes: -1. n8n application data: `/home/node/.n8n` in the container, mapped to `${volume_path}/n8n_storage/_data` on the host -2. PostgreSQL data: `/var/lib/postgresql/data` in the container, mapped to `${volume_path}/db_storage/_data` on the host +1. **n8n application data**: `/home/node/.n8n` in the container, mapped to `${volume_path}/n8n_storage/_data` on the host +2. **PostgreSQL data**: `/var/lib/postgresql/data` in the container, mapped to `${volume_path}/db_storage/_data` on the host +3. **Redis data**: `/data` in the container, mapped to `${volume_path}/redis_data` on the host +4. **n8n-mcp data**: `/app/data` in the container, mapped to `${volume_path}/n8n_mcp_storage/_data` on the host Additionally, an initialization script is mounted to the PostgreSQL container: - `/docker-entrypoint-initdb.d/init-data.sh` in the container, from `${volume_path}/init-data.sh` on the host ## Networking -The module creates a dedicated Docker network named `n8n-network` for communication between the n8n and PostgreSQL containers. The n8n container is also attached to any additional networks specified in the `networks` variable, allowing it to communicate with other services in the homelab. +The module creates a dedicated Docker network named `n8n-network` for communication between all containers. The `n8n` and `n8n-mcp` containers are also attached to any additional networks specified in the `networks` variable, allowing them to communicate with other services in the homelab. ## Integration with Networking Modules -This service is configured to be exposed through a Cloudflare tunnel for secure remote access, set by `publish_via = "tunnel"`. +The services are configured to be exposed through a Cloudflare tunnel for secure remote access, set by `publish_via = "tunnel"`. ## Example Integration in Main Configuration @@ -96,13 +119,41 @@ module "n8n" { networks = [module.services.homelab_docker_network_name] } -# The service definition is automatically included in the services output +# The service definitions are automatically included in the services output module "services" { source = "./modules/services" # ... service_definitions = [ module.n8n.service_definition, + module.n8n.n8n_mcp_service_definition, # Other service definitions ] } ``` + +## Using n8n-mcp with your IDE + +To connect your IDE to the `n8n-mcp` server, you can use the following configuration in your IDE's settings. This allows the IDE to use the n8n instance as a tool provider. + +Make sure to replace `` with your actual domain and populate the `AUTH_TOKEN` with the value of `N8N_MCP_AUTH_TOKEN` from your `.env` file. + +```json +{ + "mcpServers": { + "n8n-mcp": { + "command": "npx", + "args": [ + "mcp-remote", + "https://n8n-mcp./mcp", + "--header", + "Authorization: Bearer ${AUTH_TOKEN}", + "--transport", + "http-only" + ], + "env": { + "AUTH_TOKEN": "..." + } + } + } +} +``` diff --git a/modules/20-services-apps/n8n/main.tf b/modules/20-services-apps/n8n/main.tf index 1793ec2..59258b5 100644 --- a/modules/20-services-apps/n8n/main.tf +++ b/modules/20-services-apps/n8n/main.tf @@ -40,7 +40,7 @@ locals { monitoring = true env_file = "${path.module}/.env" n8n_internal_port = 5678 - + # Define volumes n8n_volumes = [ { @@ -118,6 +118,41 @@ locals { read_only = false } ] + + n8n_mcp_container_name = "n8n-mcp" + n8n_mcp_image = "ghcr.io/czlonkowski/n8n-mcp" + n8n_mcp_tag = "latest" + n8n_mcp_internal_port = 3000 + + n8n_mcp_volumes = [ + { + host_path = "${var.volume_path}/n8n_mcp_storage/_data" + container_path = "/app/data" + read_only = false + } + ] + + n8n_mcp_env_vars = { + MCP_MODE = "http" + USE_FIXED_HTTP = "true" + AUTH_TOKEN = provider::dotenv::get_by_key("N8N_MCP_AUTH_TOKEN", local.env_file) + N8N_API_URL = "http://${local.container_name}:${local.n8n_internal_port}" + N8N_API_KEY = provider::dotenv::get_by_key("N8N_API_KEY", local.env_file) + NODE_ENV = "production" + LOG_LEVEL = "info" + PORT = local.n8n_mcp_internal_port + NODE_DB_PATH = "/app/data/nodes.db" + REBUILD_ON_START = "false" + GENERIC_TIMEZONE = module.system_globals.timezone + } + + n8n_mcp_healthcheck = { + test = ["CMD", "curl", "-f", "http://127.0.0.1:${local.n8n_mcp_internal_port}/health"] + interval = "30s" + timeout = "10s" + retries = 3 + start_period = "40s" + } } module "n8n_network" { @@ -134,6 +169,7 @@ module "postgres" { image = local.database_image tag = local.database_tag volumes = local.database_volumes + user = "1000:1000" env_vars = local.database_env_vars networks = [module.n8n_network.name] monitoring = local.monitoring @@ -147,6 +183,7 @@ module "redis" { image = local.redis_image tag = local.redis_tag volumes = local.redis_volumes + user = "1000:1000" env_vars = { REDIS_USERNAME = "redis" REDIS_PASSWORD = "redis" @@ -164,12 +201,28 @@ module "n8n" { image = local.n8n_image tag = local.n8n_tag volumes = local.n8n_volumes + user = "1000:1000" env_vars = local.n8n_env_vars networks = concat([module.n8n_network.name], var.networks) monitoring = local.monitoring depends_on = [module.postgres, module.redis] } +# Create the n8n-mcp container +module "n8n_mcp" { + source = "../../10-services-generic/docker-service" + container_name = local.n8n_mcp_container_name + image = local.n8n_mcp_image + tag = local.n8n_mcp_tag + volumes = local.n8n_mcp_volumes + user = "1000:1000" + env_vars = local.n8n_mcp_env_vars + networks = concat([module.n8n_network.name], var.networks) + monitoring = local.monitoring + healthcheck = local.n8n_mcp_healthcheck + depends_on = [module.n8n] +} + output "service_definition" { description = "General service definition with optional ingress configuration" value = { @@ -180,3 +233,14 @@ output "service_definition" { publish_via = "tunnel" } } + +output "n8n_mcp_service_definition" { + description = "General service definition with optional ingress configuration for n8n-mcp" + value = { + name = local.n8n_mcp_container_name + primary_port = local.n8n_mcp_internal_port + endpoint = "http://${local.n8n_mcp_container_name}:${local.n8n_mcp_internal_port}" + subdomains = ["n8n-mcp"] + publish_via = "tunnel" + } +} diff --git a/services/outputs.tf b/services/outputs.tf index 0726a0f..d03bac2 100644 --- a/services/outputs.tf +++ b/services/outputs.tf @@ -12,6 +12,7 @@ output "service_definitions" { module.glance.service_definition, module.linkwarden.service_definition, module.n8n.service_definition, + module.n8n.n8n_mcp_service_definition, module.nocodb.service_definition, module.ntfy.service_definition, module.pterodactyl_wings.service_definition,