Watcher Self-Host: Infrastructure Prerequisites
This prompt gets you from "nothing provisioned yet" to a running Docker host, ready for prompts/setup.md (the guided software deployment). Give it to a coding agent (Claude Code or similar) working alongside whoever owns the cloud account or hosting environment, and drive it section by section.
Three patterns govern the agent's behavior here:
- [A]: the agent runs it. Any command that probes account state or provisions infrastructure (checking CLI installs, verifying a resource exists, running
terraform) is something the agent executes directly and interprets the output itself. It doesn't narrate the command and wait for you to type it. - [B]: the agent asks you. For values only you know and decisions only you can make: which network to deploy into, whether you want a managed database, whether you already have a TLS certificate.
- [C]: you act outside the agent's reach. For things the agent can't drive: creating a DNS validation record at your registrar, infrastructure your platform team owns and provisions on a separate timeline.
If you already have a Docker host ready and nothing else to provision, skip this file entirely and start at prompts/setup.md: its own Step 1 covers everything you need from there.
0. Confirm the hosting environment
0.1: Which cloud or hosting environment? [B]
Ask: "Where will Watcher run: AWS, another cloud provider (GCP, Azure, etc.), or your own bare-metal/on-prem infrastructure?"
AWS is the reference implementation. This bundle ships ready-to-use Terraform modules that work out of the box once you give them a network and subnet: modules/watcher-self-host/ for the compute host, IAM, security groups, and an optional managed Postgres instance, plus an optional modules/watcher-self-host-acm/ for a TLS certificate. If the answer is AWS, every section below has an "AWS (Terraform)" path: follow it as written.
For any other provider, Sections 1-6 still apply as a checklist. The underlying decisions (network, compute sizing, database, TLS, DNS) are the same everywhere, but there's no bundled module to run. Each section also has an "Any provider" path: read what needs to be true, then provision it with whatever IaC or console workflow you already use, substituting your provider's own tools (its CLI, its managed-database offering, its DNS-validated-certificate feature) for the AWS-specific ones. Skip the aws CLI commands and the two Terraform module blocks entirely on this path.
1. Tooling checks
Run these yourself before asking anything else: they tell you what's actually missing instead of guessing.
1.1: Cloud CLI [A]
AWS (Terraform):
aws --version
aws sts get-caller-identity
aws configure get region
Expected: a version string, a JSON identity block (account + ARN), and a non-empty region. If aws --version fails, the CLI isn't installed: point to https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html. If sts get-caller-identity fails, the CLI has no usable credentials: help configure aws sso login, aws configure, or an assumed role, whichever matches how this account is normally accessed. If region is empty, either set one (aws configure set region <region>) or confirm every command below will pass --region explicitly.
Any provider: run that provider's equivalent (gcloud auth list and gcloud config get-value project, az account show, etc.) and confirm both authentication and a working default project/subscription/region. There's no bundled check for this: use the provider's own CLI conventions.
1.2: Terraform CLI [A]
Needed only if you're using the AWS modules in this bundle, or writing comparable Terraform for another provider:
terraform version
Expected: Terraform v1.13 or newer (matching required_version in the modules). If missing or too old, point to https://developer.hashicorp.com/terraform/install.
2. Network
2.1: Confirm an existing network [B]
Ask: "Do you have an existing network (VPC, VNet, or equivalent) to deploy Watcher into, with a subnet that has outbound internet access: a NAT gateway, or a public subnet with a public IP?"
AWS (Terraform): the included module is bring-your-own-network: it does not create one. Capture the vpc_id and instance_subnet_id.
Any provider: capture whatever your provider calls the equivalent (VPC, VNet, subnetwork) and confirm it has egress; you'll pass this to your own IaC or console.
If there's no network yet, pause here: provisioning one is outside any of this bundle's modules and outside what an LLM session can safely improvise. Point to the platform/infra team, or the provider's own quick-start.
2.2: Verify it exists [A]
AWS (Terraform):
aws ec2 describe-vpcs --vpc-ids <their-vpc-id>
aws ec2 describe-subnets --subnet-ids <their-subnet-id>
Expected: both return a single matching resource. A NotFound error means the wrong ID, wrong account, or wrong --region/AWS_REGION: resolve before continuing.
Any provider: run the provider CLI's equivalent describe/list command, or verify directly in the console if that's simpler.
3. Postgres: bundled or managed
Ask the same question prompts/setup.md Step 1.8 covers in detail (open that file's Step 1.8 for the full operational-ownership tradeoffs): the answer here just decides how to provision it:
- Bundled (default): nothing to provision here, since the docker-compose stack brings its own Postgres.
- Managed (AWS RDS, GCP Cloud SQL, Azure Database for PostgreSQL, or similar):
- AWS (Terraform): set
create_rds = trueand providerds_subnet_ids(two subnets in different AZs) on the compute module in Section 6. - Any provider: provision the managed instance with your own IaC or console, and have its connection string ready for
prompts/setup.mdStep 4.
- AWS (Terraform): set
4. TLS certificate
Ask: "Do you want infrastructure-as-code to provision your TLS certificate, or are you handling it yourself (existing cert, Let's Encrypt, a corporate CA)?" If the latter, skip to Section 5: prompts/setup.md Step 3.1 covers the fully manual path for any provider.
AWS (Terraform): use modules/watcher-self-host-acm/. ACM certificates
are regional: request it with your AWS provider configured for the region
where the load balancer will live (prompts/setup.md Step 3.2), or the ALB
won't be able to select it:
module "watcher_cert" {
source = "./modules/watcher-self-host-acm"
domain_name = "watcher.example.com"
hosted_zone_id = "Z0123456789ABCDEFGHIJ" # omit if your DNS isn't in Route53
}
output "watcher_certificate_arn" {
value = module.watcher_cert.certificate_arn
}
output "manual_validation_records" {
value = module.watcher_cert.manual_validation_records
}
4.1: Is the hostname's DNS in Route53? [B] If yes, provide the hosted_zone_id and the module creates the validation record and waits for the certificate to become ISSUED in the same terraform apply: no manual step.
4.2: If not in Route53 [C]: omit hosted_zone_id. After terraform apply, read the validation records Terraform couldn't create itself:
terraform output -json manual_validation_records
Set each name/type/value as a DNS record at whatever provider hosts this hostname's DNS (Cloudflare, Route53, a registrar's DNS panel, corporate DNS, etc.): check that provider's documentation for how to add a raw DNS record of the given type (typically CNAME), since the exact UI/CLI steps vary by provider. Certificate issuance can take several minutes to a few hours after the record propagates. Confirm it's issued before using it:
aws acm describe-certificate --certificate-arn <arn> --query 'Certificate.Status' --output text
Expected: ISSUED. No follow-up terraform apply is needed: nothing in the module consumes the status; the check above is the source of truth.
Any provider: use that provider's own DNS-validated certificate service (e.g. GCP-managed SSL certificates, Azure App Service Certificates) or a general-purpose option like Let's Encrypt: the mechanics mirror Section 4.2 above (request, add a validation record, wait for issuance) even without a bundled module to drive it.
Whichever path, keep the certificate identifier (ARN or equivalent): it's an input to whatever reverse proxy or load balancer gets configured in prompts/setup.md Step 3.2.
5. DNS access
Confirm (if not already, from Section 4): "Do you have access to create DNS records for this hostname?" prompts/setup.md Step 3.3 needs this for the final A/ALIAS/CNAME record; if certificate validation also needed a record (Section 4.2), this access is needed earlier than Step 3.3 implies.
6. Provision the compute host
AWS (Terraform): once Sections 1-5 are settled, call modules/watcher-self-host/ with vpc_id, instance_subnet_id, ami_id, and (if applicable) create_rds/rds_subnet_ids. The full variable reference is modules/watcher-self-host/variables.tf in this bundle; the terraform apply walkthrough is on the AWS infrastructure page of the published Watcher documentation.
Terraform does not surface a module's outputs at the root, and terraform output reads state, so declare a root output for every value you will need later in the same configuration you apply here. Declaring them afterwards leaves them out of state, and reading them then reports that each output is not found:
output "watcher_instance_id" {
value = module.watcher.instance_id
}
output "watcher_rds_endpoint" {
value = module.watcher.rds_endpoint
}
output "watcher_rds_database_name" {
value = module.watcher.rds_database_name
}
output "watcher_rds_master_username" {
value = module.watcher.rds_master_username
}
output "watcher_rds_master_password" {
value = module.watcher.rds_master_password
sensitive = true
}
The four RDS outputs are null when create_rds is false. If you have already applied without them, add them and apply again; the plan should record only the new outputs and no resource changes.
Fetch the ami_id value (the current Ubuntu 24.04 AMI for the deployment's region) with:
aws ssm get-parameter \
--name /aws/service/canonical/ubuntu/server/24.04/stable/current/amd64/hvm/ebs-gp3/ami-id \
--query Parameter.Value --output text
The lookup above is for new deployments only. If you are supplying ami_id for an instance that already exists (typically after updating the module to a version that requires it), pin the AMI the instance already runs, so your first plan is a no-op instead of a replacement:
aws ec2 describe-instances --instance-ids <your instance id> \
--query 'Reservations[].Instances[].ImageId' --output text
The module pins this AMI. Changing ami_id later replaces the instance and destroys its root volume (host configuration, admin audit log, and the bundled Postgres data if you use it), so treat any change to it as a scheduled operation: back up the database, every file you created on the host, and the audit log volume first, following the backup and restore procedures on the Operations page of the published Watcher documentation, and the instance-replacement procedure on its AWS infrastructure page.
Any provider: provision a comparable VM with your own IaC or console. prompts/setup.md Step 1.1 gives sizing guidance (a 4-vCPU / 8 GB host is a reasonable starting point) that applies regardless of provider. Make sure it has Docker Engine and the Compose plugin, or a path to install them, and that whoever will run prompts/setup.md has shell access to it (SSH, or your provider's session-manager equivalent).
7. Hand off to setup.md
Once the host exists:
- AWS (Terraform): note the EC2 instance id (
terraform output -raw watcher_instance_id) for connecting via SSM inprompts/setup.mdStep 1.1. Ifcreate_rds = true, read the four connection values forprompts/setup.mdStep 4'sDATABASE_URLfrom the root outputs you declared in Section 6.-rawis required for the password, which is sensitive and is redacted by a bareterraform output:
terraform output -raw watcher_rds_endpoint
terraform output -raw watcher_rds_master_username
terraform output -raw watcher_rds_database_name
terraform output -raw watcher_rds_master_password
The endpoint already carries host:port. Store the password in the customer's secret manager before the session ends. If a certificate was provisioned in Section 4, note its ARN for Step 3.2's reverse-proxy configuration.
- Any provider: note however you'll reach the host (its address and access method), the managed-database connection string if applicable, and the certificate identifier if applicable: same three things, gathered your own way.
Move to prompts/setup.md Step 1.