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/configuration | Purpose | Important setting |
|---|---|---|
| VPC and private subnets | Keep database and app connectivity private. | App Runner VPC connector reaches RDS. |
| RDS PostgreSQL 16 | Managed relational database. | Encryption and seven-day backups. |
| S3 uploads bucket | Private object storage. | Versioning, public access block, SSE-S3. |
| ECR repository | Stores application image. | Scan on push. |
| App Runner service | Runs the container. | Instance role, VPC connector, environment variables. |
| IAM instance role | Grants runtime access. | Least privilege to uploads/*. |
| SSM SecureString | Stores database URL and auth secret. | Injected at runtime rather than image build. |
AWS deployment sequence
deploy/README.md, Terraform variables, and the AWS account/region choice.prisma migrate deploy, then starts pnpm start.TF_VAR_nextauth_url to the final public HTTPS URL and apply again so callbacks are correct.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
deploy/portable/.env.example to deploy/portable/.env and replace every CHANGE_ME value.NEXTAUTH_URL using the public HTTPS origin that will front the container.MINIO_KMS_SECRET_KEY so MinIO can satisfy the default AES256 upload encryption, or UPLOADS_S3_SSE=none to stop requesting it.docker compose up -d --build from deploy/portable.db, minio, and the one-shot minio-init setup job to complete.docker compose exec app pnpm db:seed once if demo data is desired.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 statusIf 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:
- Variables and locals explain naming, region, and environment.
- Networking creates the VPC, subnets, routes, and security boundaries.
- RDS depends on networking and stores the database.
- S3 and IAM define object storage and the runtime's least-privilege access.
- ECR stores the image and enables scan-on-push.
- SSM stores secrets without putting them in the image.
- 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.
- Build the application from the intended commit.
- Run the docs build separately; it is not the production app.
- Apply Terraform only from a reviewed state and workspace.
- Confirm the RDS security group permits the app path but not arbitrary ingress.
- Confirm the S3 bucket is private and versioning/encryption are enabled.
- Populate SSM values without printing secret contents.
- Launch the app and wait for the migration-on-start entrypoint.
- Read health/startup logs for migration completion.
- Sign in with a non-admin smoke-test account.
- Exercise one read and one authorized mutation.
- Confirm an unauthorized role receives 403.
- 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.