terraform { required_version = ">= 1.3.0" required_providers { coder = { source = "coder/coder" version = ">= 2.7" } docker = { source = "kreuzwerker/docker" version = "~> 2.25" } http = { source = "hashicorp/http" version = ">= 3.0" } } } provider "coder" {} provider "docker" { host = var.docker_socket != "" ? var.docker_socket : null } provider "http" {} # Workspace context data "coder_provisioner" "me" {} data "coder_workspace" "me" {} data "coder_workspace_owner" "me" {} # User inputs kept intentionally small so the template is easy to launch. data "coder_parameter" "project_repository" { name = "project_repository" display_name = "Project repository" description = "Optional Git URL cloned into /workspaces on first startup." default = "" mutable = true order = 1 } data "coder_parameter" "enable_services" { name = "enable_services" display_name = "Enable PostgreSQL / Redis / Qdrant" description = "Provision bundled data services inside the workspace network." type = "bool" default = "true" mutable = true order = 2 } data "coder_parameter" "enable_ai_tools" { name = "enable_ai_tools" display_name = "Install AI tooling" description = "Run the bundled AI helper scripts (Claude, Cursor, Windsurf)." type = "bool" default = "true" mutable = true order = 3 } data "coder_parameter" "enable_pgadmin" { name = "enable_pgadmin" display_name = "Expose pgAdmin" description = "Start the pgAdmin container when database services are enabled." type = "bool" default = "true" mutable = true order = 4 } data "coder_parameter" "enable_marimo" { name = "enable_marimo" display_name = "Expose Marimo" description = "Start the optional Marimo notebook container." type = "bool" default = "false" mutable = true order = 5 } data "coder_parameter" "enable_dev_endpoints" { name = "enable_dev_endpoints" display_name = "Expose Dev HTTP Ports" description = "Create Development Services app shortcuts for localhost:3000, 5173, and 8000." type = "bool" default = "false" mutable = true order = 6 } data "coder_parameter" "enable_jetbrains" { name = "enable_jetbrains" display_name = "JetBrains Gateway" description = "Install JetBrains Gateway integration for this workspace." type = "bool" default = "true" mutable = true order = 7 } data "coder_parameter" "ai_prompt" { name = "AI Prompt" display_name = "AI Task Prompt" description = "Optional pre-filled prompt shown when starting a Claude Code task." type = "string" default = "" mutable = true order = 8 form_type = "textarea" } locals { bind_mounts = [ { host = "${var.host_home_path}", container = "/home/coder" }, { host = "/var/run/docker.sock", container = "/var/run/docker.sock" }, ] workspace_id = data.coder_workspace.me.id container_name = "coder-${local.workspace_id}" git_author_name = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name) git_author_email = data.coder_workspace_owner.me.email project_repo_url = trimspace(data.coder_parameter.project_repository.value) gitea_pat = trimspace(var.gitea_pat) github_pat = trimspace(var.github_pat) services_enabled = data.coder_parameter.enable_services.value pgadmin_enabled = data.coder_parameter.enable_pgadmin.value marimo_enabled = data.coder_parameter.enable_marimo.value dev_endpoints_enabled = data.coder_parameter.enable_dev_endpoints.value ai_enabled = data.coder_parameter.enable_ai_tools.value port_forwarding = local.services_enabled || local.marimo_enabled postgres_url = "postgresql://postgres:${var.postgres_password}@postgres-${local.workspace_id}:5432/postgres" redis_url = "redis://:${var.redis_password}@redis-${local.workspace_id}:6379" qdrant_url = "http://qdrant-${local.workspace_id}:6333" agent_startup = join("\n", compact([ "set -eu", "export CODER_WORKSPACE_ID=${local.workspace_id}", "# Fix RVM environment variables to suppress warnings", "if printf '%s' \"$PATH\" | tr ':' '\n' | grep -q '/usr/share/rvm'; then", " OLD_IFS=$IFS", " IFS=':'", " NEW_PATH=\"\"", " for segment in $$PATH; do", " case \"$segment\" in", " *'/usr/share/rvm'* ) continue ;;", " * ) if [ -z \"$$NEW_PATH\" ]; then NEW_PATH=\"$$segment\"; else NEW_PATH=\"$$NEW_PATH:$$segment\"; fi ;;", " esac", " done", " IFS=$OLD_IFS", " if [ -n \"$$NEW_PATH\" ]; then export PATH=\"$$NEW_PATH\"; fi", "fi", "export GEM_HOME=\"$HOME/.gem\"", "export GEM_PATH=\"$HOME/.gem\"", "export rvm_silence_path_mismatch_check_flag=1", "export RVM_SILENCE_PATH_MISMATCH_CHECK_FLAG=1", "export rvmsudo_secure_path=1", "export RVMSUDO_SECURE_PATH=1", "# Ensure required directories exist", "mkdir -p /home/coder/code-tools/terraform/scripts", "mkdir -p /home/coder/code-tools/terraform/scripts/agentapi", "# Verify Python 3.12+ baked into the image (checking as coder user)", "if ! sudo --preserve-env=CODER_WORKSPACE_ID,CODER_WORKSPACE_REPO,GITEA_PAT,GITHUB_PAT -u coder env -i HOME=/home/coder PATH=\"$PATH\" GEM_HOME=/home/coder/.gem GEM_PATH=/home/coder/.gem CODER_WORKSPACE_ID=\"$${CODER_WORKSPACE_ID-}\" CODER_WORKSPACE_REPO=\"$${CODER_WORKSPACE_REPO-}\" GITEA_PAT=\"$${GITEA_PAT-}\" GITHUB_PAT=\"$${GITHUB_PAT-}\" rvm_silence_path_mismatch_check_flag=1 RVM_SILENCE_PATH_MISMATCH_CHECK_FLAG=1 rvmsudo_secure_path=1 RVMSUDO_SECURE_PATH=1 bash -lc 'command -v python3 >/dev/null 2>&1 && python3 -c \"import sys; exit(0) if sys.version_info >= (3, 12) else exit(1)\"'; then", " echo 'Python 3.12+ not detected for coder user; update the base image.'", "fi", "mkdir -p /workspaces", "chown -R coder:coder /workspaces || echo 'Cannot change workspace ownership'", "# SSL Certificate setup (running as root)", "if [ -f /home/coder/lab-certs/lab.crt ]; then", " echo 'Installing SSL certificate for lab.crt'", " # Running as root, so direct access to system directories", " cp /home/coder/lab-certs/lab.crt /usr/local/share/ca-certificates/lab.crt 2>/dev/null && echo 'SSL cert copied successfully' || echo 'Cannot copy SSL cert'", " update-ca-certificates 2>/dev/null && echo 'CA certificates updated successfully' || echo 'Cannot update ca-certificates'", " # Configure git globally for coder user", " sudo --preserve-env=CODER_WORKSPACE_ID,CODER_WORKSPACE_REPO,GITEA_PAT,GITHUB_PAT -u coder env -i HOME=/home/coder PATH=\"$PATH\" GEM_HOME=/home/coder/.gem GEM_PATH=/home/coder/.gem CODER_WORKSPACE_ID=\"$${CODER_WORKSPACE_ID-}\" CODER_WORKSPACE_REPO=\"$${CODER_WORKSPACE_REPO-}\" GITEA_PAT=\"$${GITEA_PAT-}\" GITHUB_PAT=\"$${GITHUB_PAT-}\" rvm_silence_path_mismatch_check_flag=1 RVM_SILENCE_PATH_MISMATCH_CHECK_FLAG=1 rvmsudo_secure_path=1 RVMSUDO_SECURE_PATH=1 git config --global http.\"https://git.lab\".sslCAInfo /home/coder/lab-certs/lab.crt || echo 'Cannot configure git ssl'", " sudo --preserve-env=CODER_WORKSPACE_ID,CODER_WORKSPACE_REPO,GITEA_PAT,GITHUB_PAT -u coder env -i HOME=/home/coder PATH=\"$PATH\" GEM_HOME=/home/coder/.gem GEM_PATH=/home/coder/.gem CODER_WORKSPACE_ID=\"$${CODER_WORKSPACE_ID-}\" CODER_WORKSPACE_REPO=\"$${CODER_WORKSPACE_REPO-}\" GITEA_PAT=\"$${GITEA_PAT-}\" GITHUB_PAT=\"$${GITHUB_PAT-}\" rvm_silence_path_mismatch_check_flag=1 RVM_SILENCE_PATH_MISMATCH_CHECK_FLAG=1 rvmsudo_secure_path=1 RVMSUDO_SECURE_PATH=1 git config --global http.\"https://git.lab\".sslVerify true || echo 'Cannot configure git ssl verify'", "else", " echo 'SSL cert not available at /home/coder/lab-certs/lab.crt'", " # Configure git to skip SSL verification for git.lab if no cert available", " sudo --preserve-env=CODER_WORKSPACE_ID,CODER_WORKSPACE_REPO,GITEA_PAT,GITHUB_PAT -u coder env -i HOME=/home/coder PATH=\"$PATH\" GEM_HOME=/home/coder/.gem GEM_PATH=/home/coder/.gem CODER_WORKSPACE_ID=\"$${CODER_WORKSPACE_ID-}\" CODER_WORKSPACE_REPO=\"$${CODER_WORKSPACE_REPO-}\" GITEA_PAT=\"$${GITEA_PAT-}\" GITHUB_PAT=\"$${GITHUB_PAT-}\" rvm_silence_path_mismatch_check_flag=1 RVM_SILENCE_PATH_MISMATCH_CHECK_FLAG=1 rvmsudo_secure_path=1 RVMSUDO_SECURE_PATH=1 git config --global http.\"https://git.lab\".sslVerify false || echo 'Cannot configure git ssl skip'", "fi", "# End SSL setup", "export ENABLE_PGADMIN=${tostring(local.pgadmin_enabled)}", "export ENABLE_MARIMO=${tostring(local.marimo_enabled)}", local.port_forwarding ? "if [ -x /home/coder/code-tools/terraform/scripts/port-forward.sh ]; then bash /home/coder/code-tools/terraform/scripts/port-forward.sh; else echo \"Port forwarding script not found\"; fi" : "echo 'No service port forwarding requested'" ])) } # Workspace network keeps the workspace stack isolated from the host. resource "docker_network" "workspace" { name = "coder-${local.workspace_id}" driver = "bridge" labels { label = "coder.workspace_id" value = local.workspace_id } labels { label = "coder.owner" value = data.coder_workspace_owner.me.name } } # Persistent workspace data volume mounted at /workspaces inside the container. resource "docker_volume" "workspaces" { name = "workspaces-${local.workspace_id}" labels { label = "coder.workspace_id" value = local.workspace_id } labels { label = "coder.type" value = "workspace-data" } } # Separate persistent home directory for the coder user. # Base development container image (customise via terraform.tfvars). resource "docker_image" "devcontainer" { name = var.devcontainer_image keep_locally = true }