services: localstack: container_name: "${LOCALSTACK_DOCKER_NAME:-localstack-main}" image: localstack/localstack ports: - "127.0.0.1:4566:4566" # LocalStack Gateway - "127.0.0.1:4510-4559:4510-4559" # external services port range - "127.0.0.1:443:443" # LocalStack HTTPS Gateway environment: # Activate LocalStack for AWS: https://docs.localstack.cloud/getting-started/auth-token/ - LOCALSTACK_AUTH_TOKEN=${LOCALSTACK_AUTH_TOKEN:?} # required for Pro # LocalStack configuration: https://docs.localstack.cloud/references/configuration/ - DEBUG=${DEBUG:-0} - PERSISTENCE=${PERSISTENCE:-0} # Queue URLs are handed out by LocalStack and then connected to by the # cms container. The default "standard" strategy builds them on # sqs..localhost.localstack.cloud, which public DNS points at # 127.0.0.1 and which this network has no alias for — so cms would fail # to resolve its own queue. "off" keeps them on the gateway host, the # one name that resolves from the host and from inside the network. - SQS_ENDPOINT_STRATEGY=off volumes: - "${LOCALSTACK_VOLUME_DIR:-./volume}:/var/lib/localstack" - "/var/run/docker.sock:/var/run/docker.sock" networks: default: aliases: # Publicly this name resolves to 127.0.0.1, so the endpoint in # infrastructure/Pulumi.local.yaml works from the host; the alias # makes the same name resolve here from inside the network. - localhost.localstack.cloud # S3 Control prefixes its endpoint host with the caller's account id # — the SDK does this even for a custom endpoint — so the bucket-tag # read goes to .localhost.localstack.cloud. The public # wildcard points that at 127.0.0.1, which inside the network is the # calling container, not this one. LocalStack's account is always # 000000000000, so one more alias covers it. - 000000000000.localhost.localstack.cloud # Provisions the buckets, the Postgres instance and the IAM roles inside # LocalStack, by running infrastructure/ — the same program that deploys # production, on its "local" stack. Runs to completion before cms starts. # Equivalent to `pulumi stack select local && pulumi up` from the host. infra: image: pulumi/pulumi-go:latest working_dir: /infra entrypoint: ["/bin/sh", "-c"] # One list element on purpose: a string command is split on whitespace, and # `sh -c` would then take just the first word as its script. command: - > pulumi login file:///state && pulumi stack select local --create && pulumi up --yes --non-interactive environment: - PULUMI_CONFIG_PASSPHRASE=${PULUMI_CONFIG_PASSPHRASE:-local} - PULUMI_SKIP_UPDATE_CHECK=true - DB_PASSWORD=example_password volumes: - "./infrastructure:/infra" - "pulumi-state:/state" # stack state, kept out of the repo - "pulumi-home:/root/.pulumi" # provider plugins, downloaded once - "go-mod-cache:/go/pkg/mod" depends_on: localstack: condition: service_healthy develop: watch: # The program is bind-mounted, not baked into the image, so there is # nothing to rebuild — the restart is what re-runs `pulumi up`. - action: sync+restart path: ./infrastructure target: /infra ignore: - README.md cms: container_name: "${CMS_DOCKER_NAME:-cms}" build: context: ./cms dockerfile: Dockerfile ports: - "127.0.0.1:8081:8081" # JSON API + Swagger UI at /swagger/ environment: # AWS: point the SDK at LocalStack instead of the real endpoints. This # host is deliberately not "localstack": it is baked into the presigned # upload URLs this service hands out, so it has to resolve for whoever # PUTs the file — on the host as well as inside this network. - AWS_REGION=${AWS_REGION:-us-east-1} - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-test} - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-test} - AWS_ENDPOINT_URL=${AWS_ENDPOINT_URL:-http://localhost.localstack.cloud:4566} # Bucket and role wiring — see main.go, which panics if any is empty. The # bucket names are pinned by the localstack branch in infrastructure/main.go. - S3_BUCKET=${S3_BUCKET:-raw-uploads-bucket} - MEDIACONVERT_INPUT_BUCKET=${MEDIACONVERT_INPUT_BUCKET:-raw-uploads-bucket} - MEDIACONVERT_OUTPUT_BUCKET=${MEDIACONVERT_OUTPUT_BUCKET:-encoded-bucket} - MEDIACONVERT_ROLE_ARN=${MEDIACONVERT_ROLE_ARN:-arn:aws:iam::000000000000:role/mediaconvert-service-role} # The queue the job-events topic fans out to, consumed for the lifetime # of the process. Pinned by the localstack branch in infrastructure/main.go; # LocalStack always uses account 000000000000. - MEDIACONVERT_EVENTS_QUEUE_URL=${MEDIACONVERT_EVENTS_QUEUE_URL:-http://localhost.localstack.cloud:4566/000000000000/cms-mediaconvert-events} # The topic ready videos are announced on, which fans out to the read # side's queue. Pinned by the localstack branch in infrastructure/main.go. - CATALOGUE_EVENTS_TOPIC_ARN=${CATALOGUE_EVENTS_TOPIC_ARN:-arn:aws:sns:us-east-1:000000000000:catalogue-events} # Where finished HLS output is served from. In AWS this is the CloudFront # distribution; there is none under LocalStack, so the encoded bucket is # addressed directly — path-style, for the same reason S3 is elsewhere. - PLAYBACK_BASE_URL=${PLAYBACK_BASE_URL:-http://localhost.localstack.cloud:4566/encoded-bucket} # Postgres: the RDS instance LocalStack provisions, which runs inside the # localstack container and speaks plain TCP — hence sslmode=disable. - DB_HOST=${DB_HOST:-localhost.localstack.cloud} - DB_PORT=${DB_PORT:-4510} - DB_NAME=${DB_NAME:-cms} - DB_USER=${DB_USER:-cms} - DB_PASSWORD=example_password - DB_SSLMODE=${DB_SSLMODE:-disable} depends_on: infra: condition: service_completed_successfully develop: watch: # cms is a compiled binary, so there is nothing useful to sync into the # running container — every change means a new image. Needs # `docker compose up --watch` (or `docker compose watch`) to take effect. - action: rebuild path: ./cms # Stands in for the ElastiCache node infrastructure/main.go provisions on the # AWS stacks. Not provisioned through LocalStack like the buckets and the # database are: ElastiCache hands out an endpoint on a port it chooses, and # the literal REDIS_ADDR below has to be known before anything is created. # Nothing here is worth persisting — every entry is derivable from Postgres, # which is the whole point of a cache — so there is no volume. redis: container_name: "${REDIS_DOCKER_NAME:-redis}" image: redis:7-alpine ports: - "127.0.0.1:6379:6379" healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 3s retries: 10 discovery: container_name: "${DISCOVERY_DOCKER_NAME:-discovery}" build: context: ./discovery dockerfile: Dockerfile ports: - "127.0.0.1:8080:8080" # JSON API + Swagger UI at /swagger/ environment: # AWS: discovery reaches exactly one AWS API — the queue subscribed to # cms's catalogue topic, which it drains for the lifetime of the process. # That is why it now has a task role in infrastructure/main.go, scoped to # this queue and nothing else. - AWS_REGION=${AWS_REGION:-us-east-1} - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-test} - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-test} - AWS_ENDPOINT_URL=${AWS_ENDPOINT_URL:-http://localhost.localstack.cloud:4566} - CATALOGUE_EVENTS_QUEUE_URL=${CATALOGUE_EVENTS_QUEUE_URL:-http://localhost.localstack.cloud:4566/000000000000/discovery-catalogue-events} # The catalogue search's cache — the redis service below, standing in for # ElastiCache. main.go panics without an address but only warns if the # node does not answer: a cold cache means slower searches, not none. - REDIS_ADDR=${REDIS_ADDR:-redis:6379} # Postgres: its own role and database, provisioned alongside the cms ones # by newServiceDatabase, so the two services share no credentials. - DB_HOST=${DISCOVERY_DB_HOST:-localhost.localstack.cloud} - DB_PORT=${DISCOVERY_DB_PORT:-4510} - DB_NAME=${DISCOVERY_DB_NAME:-discovery} - DB_USER=${DISCOVERY_DB_USER:-discovery} - DB_PASSWORD=example_password - DB_SSLMODE=${DISCOVERY_DB_SSLMODE:-disable} depends_on: infra: condition: service_completed_successfully redis: condition: service_healthy develop: watch: # Same as cms: a compiled binary, so every change means a new image. # Needs `docker compose up --watch` (or `docker compose watch`). - action: rebuild path: ./discovery volumes: pulumi-state: pulumi-home: go-mod-cache: