Project ROUNDTABLE Docs

Deployment

AWS managed deployment and portable non-AWS deployment

Deployment packages the same Next.js application for two operating contexts. The AWS path uses App Runner, RDS, S3, ECR, Terraform, and SSM Parameter Store. The portable path uses a container host, PostgreSQL, MinIO or another S3-compatible store, and a reverse proxy.

AWS resource map

Resource/configurationPurposeImportant setting
VPC and private subnetsKeep database and app connectivity private.App Runner VPC connector reaches RDS.
RDS PostgreSQL 16Managed relational database.Encryption and seven-day backups.
S3 uploads bucketPrivate object storage.Versioning, public access block, SSE-S3.
ECR repositoryStores application image.Scan on push.
App Runner serviceRuns the container.Instance role, VPC connector, environment variables.
IAM instance roleGrants runtime access.Least privilege to uploads/*.
SSM SecureStringStores database URL and auth secret.Injected at runtime rather than image build.

AWS deployment sequence

Review deploy/README.md, Terraform variables, and the AWS account/region choice.
Build and push the application image to the provisioned ECR repository.
Apply Terraform to create networking, RDS, S3, ECR, App Runner, IAM, and SSM resources.
Confirm App Runner can reach RDS through the VPC connector and can access the bucket through its instance role.
The container entrypoint runs prisma migrate deploy, then starts pnpm start.
Set TF_VAR_nextauth_url to the final public HTTPS URL and apply again so callbacks are correct.
Seed production manually from an approved network path; migrations and seeding are separate operations.

First apply can fail to launch an app before an image exists; the runbook calls out this bootstrap ordering. Do not put a production seed password or plaintext SSM secret in Terraform source.

Optional Bedrock matching

Set matching_scorer = "bedrock" to enable embedding scoring in the AWS stack. Terraform then grants the App Runner instance role bedrock:InvokeModel on a foundation-model ARN for bedrock_embedding_model_id and creates a bedrock-runtime VPC interface endpoint. The private subnets have no NAT gateway, so the interface endpoint is required for the application to reach the Bedrock runtime.

bedrock_embedding_model_id must be a foundation-model ID. Terraform rejects cross-region inference-profile IDs with a us., eu., apac., or us-gov. prefix during plan validation; those IDs do not match the foundation-model ARN in the IAM policy and would be denied at runtime.

Portable deployment sequence

Copy deploy/portable/.env.example to deploy/portable/.env and replace every CHANGE_ME value.
Set a real NEXTAUTH_URL using the public HTTPS origin that will front the container.
Provide a bucket name and credentials with access limited to the upload bucket, plus either MINIO_KMS_SECRET_KEY so MinIO can satisfy the default AES256 upload encryption, or UPLOADS_S3_SSE=none to stop requesting it.
Run docker compose up -d --build from deploy/portable.
Wait for db, minio, and the one-shot minio-init setup job to complete.
Check app logs for migration completion, then run docker compose exec app pnpm db:seed once if demo data is desired.
Terminate TLS in a reverse proxy and forward the public URL to the app container.

Compose migration-on-start is not the same as seeding. A migration updates structure; seeding inserts demo users and records. Keep seed credentials out of a production-like environment.

Runtime checks

docker compose ps
docker compose logs app
docker compose exec app pnpm prisma migrate status

If MinIO uploads fail while text-only submissions work, inspect virtual-host addressing, DNS for MINIO_DOMAIN, bucket policy, and the encryption pair — UPLOADS_S3_SSE on the app against the store's SSE/KMS support. If NextAuth redirects incorrectly, inspect NEXTAUTH_URL first.

Operational boundary

The current deployment guidance is a pilot deployment, not an IL5 authorization package. IL5 hardening, CAC/FlankSpeed federation, continuous delivery controls, and production operational monitoring require additional work and review.

Terraform reading strategy

Read Terraform from dependency outward:

  1. Variables and locals explain naming, region, and environment.
  2. Networking creates the VPC, subnets, routes, and security boundaries.
  3. RDS depends on networking and stores the database.
  4. S3 and IAM define object storage and the runtime's least-privilege access.
  5. ECR stores the image and enables scan-on-push.
  6. SSM stores secrets without putting them in the image.
  7. App Runner connects the image, role, environment, and VPC connector.

If a resource is absent from the Terraform code, do not document it as provisioned. A managed email service or external web application firewall may be useful future infrastructure but is not automatically part of this repository's deployment.

Migration-on-start caution

The entrypoint's migration step is convenient for a pilot because a new image can apply committed migrations before serving traffic. It requires the database to be reachable and the runtime role to have the expected connection, plus permission to run CREATE EXTENSION IF NOT EXISTS pg_trgm for the trigram indexes behind typo-tolerant search. Two distinct outcomes follow from that statement. If it runs and the role lacks the right, the migration fails loudly and startup should stop — unless a DBA pre-installed the extension, in which case the statement is a privilege-free no-op and the migration succeeds. If it never runs at all — or, less likely, the extension is later dropped with CASCADE, taking the trigram indexes with it — nothing fails: only the fuzzy stage degrades to substring matching. That second case is silent, so verify with select extname from pg_extension where extname = 'pg_trgm'; after every deploy. A failed migration should stop startup rather than silently serving a schema-incompatible application.

Deployment handoff checklist

Before handing an environment to another operator, record the image tag, migration revision, database endpoint, object-storage endpoint, and public NEXTAUTH_URL. These values explain which code and configuration a browser session is exercising.

  1. Build the application from the intended commit.
  2. Run the docs build separately; it is not the production app.
  3. Apply Terraform only from a reviewed state and workspace.
  4. Confirm the RDS security group permits the app path but not arbitrary ingress.
  5. Confirm the S3 bucket is private and versioning/encryption are enabled.
  6. Populate SSM values without printing secret contents.
  7. Launch the app and wait for the migration-on-start entrypoint.
  8. Read health/startup logs for migration completion.
  9. Sign in with a non-admin smoke-test account.
  10. Exercise one read and one authorized mutation.
  11. Confirm an unauthorized role receives 403.
  12. Record rollback and migration-recovery instructions.

Portable deployment operator notes

Compose is useful for a reproducible local or isolated pilot, but it does not turn a laptop into an authorized Navy environment. Put TLS termination in front of the app, keep Postgres and MinIO on a private network, and use an HTTPS NEXTAUTH_URL for any non-local browser.

MinIO's S3 compatibility depends on endpoint and bucket addressing. If the application constructs a virtual-hosted bucket URL, DNS must resolve the bucket host to MinIO; and when the app requests server-side encryption via UPLOADS_S3_SSE, the local KMS configuration must be able to serve it — with UPLOADS_S3_SSE=none no KMS configuration is involved. A successful MinIO process alone does not prove that uploads will work.