EMmind MTL infrastructure and deployment
Evidence snapshot: 2026-08-26. Repository evidence uses the commits listed in the project overview. Live resources for public EMmind and EMmind MTL were checked read-only in AWS accounts
050752624503and564141170168, regionap-southeast-1.
This page maps both infrastructure boundaries where they meet. The detailed runtime map is for MTL. The public stack is recorded separately because it owns the original Studio Twist build and deployment pipeline referenced by the MTL repository workflows.
Infrastructure boundary
The live application runs on EC2, not ECS. Each environment registers the same EC2 instance with three instance-type target groups. HTTPS host and path rules select the core, employee web, or CMS target. The three application repositories describe the containers, service dependencies, image publication, and deployment-event contract, but contain no definitions for these live compute, load-balancer, DNS, database, cache, bucket, or secret-delivery resources. The repository or team that owns those definitions was not established.
Environment resource map
| Resource | Development | Production |
|---|---|---|
| Employee application and API host | dev.mtl.emmind.aimet.tech | mtl.emmind.aimet.tech |
| CMS host | dev.cms.mtl.emmind.aimet.tech | cms.mtl.emmind.aimet.tech |
| Application Load Balancer | dev-alb | emmind-mtl-prod |
| EC2 host | emmind-mtl-dev (i-062e6284f3b20bdff) | emmind-mtl-prod (i-0fd95a3c2cede9828) |
| VPC | Default VPC vpc-ae7685c8 | dmind-prod-vpc (vpc-00b2df00a723fbfe9) |
| PostgreSQL RDS | dev-db, port 5432 | emmind-mtl-prod, port 5432 |
| System/assets bucket | emmind-mtl-assets-dev | emmind-mtl-assets |
| User-content bucket | emmind-mtl-usercontent-dev | emmind-mtl-usercontent |
| EC2 instance role/profile | emmind-mtl-dev-ec2 | emmind-mtl-prod-ec2 |
| Redis | Live placement not established | Live placement not established |
Security-group relationships allow each application host to reach its environment's RDS instance on TCP 5432. EC2 role policies establish access to the listed S3 buckets. The production RDS endpoint is emmind-mtl-prod.cax9kybodeyp.ap-southeast-1.rds.amazonaws.com; use the configured DSN rather than embedding this address in application code.
| Listener selection | Target group | Target port | Load-balancer health path |
|---|---|---|---|
Employee host and /api/* | emmind-mtl-core-dev or emmind-mtl-core-prod | 3001 | /api/health-check |
| Remaining paths on employee host | emmind-mtl-webapp-dev or emmind-mtl-webapp-prod | 3000 | /liff |
| CMS host | emmind-mtl-cmsapp-dev or emmind-mtl-cmsapp-prod | 3002 | /health-check |
The production listener's default action also forwards to the employee web target. The development listener relies on its host-based rules for the verified application hosts.
Public EMmind infrastructure reference
Public EMmind runs entirely in account 050752624503:
| Resource | Public deployment |
|---|---|
| VPC | cbt-vpc (vpc-0e2d0a6656f56e8fe) |
| Application Load Balancer | Internet-facing cbt-alb |
| EC2 application hosts | cbt-dev, cbt-uat, and cbt-prod |
| Target groups | cbt-core-* on port 3001, cbt-webapp-* on port 3000, and cbt-cmsapp-* on port 3002 |
| Production hosts | emmind.aimet.tech and cms.emmind.aimet.tech |
| UAT hosts | cbt.aimet.tech and cms.cbt.aimet.tech |
| PostgreSQL | Aurora PostgreSQL cluster db-1, engine 16.11 |
| System/assets buckets | cbtapp-assets-dev and cbtapp-assets |
| User-content buckets | cbtapp-usercontent-dev and cbtapp-usercontent |
The HTTPS listener sends /api/* on the employee host to core, remaining employee-host paths to the web app, and the CMS host to the CMS target. The account also owns the emmind.aimet.tech hosted zone and delegates the mtl.emmind.aimet.tech subdomain to account 564141170168.
Runtime components
| Component | Runtime | Container default in source | Live target port | Required services |
|---|---|---|---|---|
| Core API and scheduled jobs | NestJS on Node.js 22.12.0-alpine | 3000 | 3001 | PostgreSQL, Redis, S3, LINE APIs, Google OAuth |
| Employee web app | Remix on Node.js 20.17.0-alpine | 3000 | 3000 | Core API, Redis session store, LINE LIFF |
| CMS | React Router on Node.js 22.13.0-alpine | 3000 | 3002 | Core API |
All container entrypoints load environment values from /data/secrets/env. The core starts with yarn start:prod; its dbmigrate entrypoint mode runs TypeORM migrations. The employee and CMS containers run their server-rendered web servers. The AWS control plane shows the target ports but not the host's current Docker arguments or image tags, so the exact container-to-host port mappings and active image versions were not verified.
Build and image publication
The public and MTL editions use separate build projects and ECR namespaces.
| Edition | CodeBuild projects | GitHub source | Development branch and tag | ECR repositories |
|---|---|---|---|---|
| Public | cbt-core-build, cbt-webapp-build, cbt-cmsapp-build | studiotwist/cbt-core, studiotwist/cbt-webapp, studiotwist/cbt-cmsapp | develop -> develop | cbt/cbt-core, cbt/cbt-webapp, cbt/cbt-cmsapp in account 050752624503 |
| MTL | emmind-core-build, emmind-webapp-build, emmind-cmsapp-build | Aimet cbt-core, cbt-webapp, cbt-cmsapp | dev -> dev | emmind/emmind-core, emmind/emmind-webapp, emmind/emmind-cmsapp in account 564141170168 |
Public CodeBuild webhooks accept the develop branch and Git tags. The public buildspecs also tag the image as latest. On a successful develop build, they publish a deployment request to cbt-devops-deploy-events.
MTL CodeBuild webhooks accept the dev branch and Git tags. The MTL buildspecs publish the selected tag and resolved commit SHA but have the original SNS notification and automatic deployment block commented out.
The MTL ECR repositories and their development and version tags were verified in account 564141170168.
| Repository | CodeBuild project | ECR image | Development image | Release image |
|---|---|---|---|---|
cbt-core | emmind-core-build | emmind/emmind-core | :dev from the dev branch | Matching Git tag |
cbt-webapp | emmind-webapp-build | emmind/emmind-webapp | :dev from the dev branch | Matching Git tag |
cbt-cmsapp | emmind-cmsapp-build | emmind/emmind-cmsapp | :dev from the dev branch | Matching Git tag |
Each CodeBuild project points to its matching GitHub repository, uses the root buildspec.yml, and shares the emmind-code-build service role. Active CodeBuild webhooks accept pushes to refs/heads/dev and Git tag refs. For the development branch, each buildspec sets the image tag to dev; for tag refs, it uses the Git tag. It also tags the image with CODEBUILD_RESOLVED_SOURCE_VERSION and attempts that push without failing the build if the SHA push fails.
Deployment event contract
Each repository has an active, manually dispatched GitHub workflow. Source configures it to assume CBTGithubDeployerRole in AWS account 050752624503 and send an SNS message to cbt-devops-deploy-events.
{
"app_name": "cbt-core | cbt-webapp | cbt-cmsapp",
"app_env": "operator input",
"app_image_tag": "operator input"
}The SNS topic and its consumer are part of the public Studio Twist deployment pipeline. The topic invokes Lambda CBT-Devops-Deploy in account 050752624503. That function:
- selects an EC2 instance in the same account using
App_<app_name>=trueandApp_env=<environment>tags; - runs an SSM shell command under
/app/<app_name>; - updates the image tag in
docker-compose.yml, logs in to the public ECR registry, pulls the image, and runsdocker compose up -d --remove-orphans; - runs the core database migration on an instance tagged for migrations; and
- invokes
CBT-Devops-PostDeployafter a successful deployment to check target-group health.
This consumer resolves cbt-* application names to the public cbt/cbt-* images and the public cbt-dev, cbt-uat, or cbt-prod hosts. It does not map requests to the MTL emmind/emmind-* images or to EC2 instances in account 564141170168.
The manual workflows in the MTL repositories still publish cbt-* names to this public topic. The reviewed source and AWS resources therefore do not establish those workflows as the mechanism that activates MTL images. MTL image publication is verified; MTL runtime activation remains a separate ownership gap.
Configuration ownership
Core API
| Group | Configuration |
|---|---|
| HTTP | PORT |
| PostgreSQL | DB_DSN or split DB_HOST, DB_PORT, DB_USER, DB_PASS, and DB_NAME settings, plus optional DB_SCHEMA and DB_SSL |
| Redis | Host, port, and optional LINE conversation-state expiry |
| JWT | Base64-encoded access-token public/private PEM keys and refresh-token private PEM key. A refresh public-key constant exists but is not consumed by current source. |
| LINE | Login client ID, LIFF ID, and Messaging API channel token |
| OAuth client ID, client secret, redirect URI, and verification expiry | |
| AWS and S3 | Region, system bucket, user-content bucket, and the AWS SDK credential chain |
Employee web app
| Group | Configuration |
|---|---|
| Session | Cookie name, signing secret, secure flag, and CSRF secret |
| Core API | Base URL that reaches the core's globally prefixed /api routes |
| LINE | LIFF ID, LIFF URL, and endpoint URL |
| Redis | REDIS_DSN for server-side employee sessions |
| Assets and analytics | CDN base URL and Google Analytics ID |
| Local development | HTTPS toggle used by the LIFF development setup |
CMS
The CMS uses a cookie name, cookie secret, secure flag, CSRF secret, and core API base URL. Its browser monitoring reads VITE_NODE_ENV and VITE_SENTRY_DSN. A Redis service exists in the local compose file, but the current CMS session is stored in a signed cookie.
No application-specific Secrets Manager secret or Systems Manager Parameter Store name was identified in the verified account. /data/secrets/env is a container-level contract; the mechanism that creates or mounts that file on the EC2 hosts remains unknown.
Database migrations
cbt-core owns TypeORM entities and migrations. Runtime schema synchronization and automatic migration execution are disabled. Run the core image in migration mode for schema changes:
/app/docker-entrypoint.sh dbmigrateMigration execution belongs in the deployment sequence before application tasks that require the new schema.
Scheduled infrastructure work
Scheduled jobs run inside cbt-core, not in a separate worker repository.
| Schedule | Work |
|---|---|
| Daily at midnight | Copy recurring notified activities into the current journal |
| Every five minutes | Send due activity and 9Q notifications through LINE |
| Daily at 08:00, Asia/Bangkok | Send streak and seven-day emotion reminders |
| Daily at 03:00, Asia/Bangkok | Reset streak state and re-enable eligible emotion reminders |
Health and telemetry
| Application | Application health route | ALB health path | CloudWatch application log groups | Telemetry in source |
|---|---|---|---|---|
| Core | GET /api/health-check | /api/health-check | /emmind/docker/emmind-core-dev, /emmind/docker/emmind-core-prod | HTTP request logging and global exception logging. The Sentry package and debug route exist, but no initialization was found. |
| Employee web | /health-check | /liff | /emmind/docker/emmind-webapp-dev, /emmind/docker/emmind-webapp-prod | Sentry server error handling and Google Analytics when configured |
| CMS | /health-check | /health-check | /emmind/docker/emmind-cmsapp-dev, /emmind/docker/emmind-cmsapp-prod | Browser Sentry in the configured production mode |
MTL CodeBuild writes to /aws/codebuild/emmind-core-build, /aws/codebuild/emmind-webapp-build, and /aws/codebuild/emmind-cmsapp-build. Public CodeBuild writes to /aws/codebuild/cbt-core-build, /aws/codebuild/cbt-webapp-build, and /aws/codebuild/cbt-cmsapp-build.
Related deployed property
qr.mtl.emmind.aimet.tech is a static QR entry page served by CloudFront distribution E3QXBPOIIJYCY from the emmind-mtl-qr-page S3 bucket. Route 53 aliases the hostname to the distribution. Its source repository and infrastructure-definition owner were not identified.
Verified infrastructure limits
No CBT-mapped ECS service or task family, Auto Scaling group, CodeDeploy application, CodePipeline pipeline, API Gateway API, or Cloud Map service was identified. No CloudFormation stack was mapped to the verified resources. These negative findings narrow the current operating model to EC2, ALB, RDS, S3, ECR, CodeBuild, CloudWatch, and the external deployment-event path; they do not prove that infrastructure definitions do not exist in another account or repository.
Local service map
| Repository | Local service | Default host port |
|---|---|---|
| Core | PostgreSQL | 5450 |
| Core | Redis | 6364 |
| Employee web | Redis | 6377 |
| CMS | Redis compose service, not used by the current session code | 6371 |
The core Makefile starts PostgreSQL and Redis. Running the employee application locally also requires its Redis session store and a reachable core API.