Tithe, Stewardship & Hermeneutical Distance — Knowledge Graph Research assistant
← All nodes

AWS Deployment Proposal

proposal draft for review — architecture and sizi…

AWS Deployment Proposal

Infrastructure Proposal — Draft for Review. Moving the tithe/stewardship knowledge-graph viewer and its research-assistant chatbot from a local Python process to ECS on Fargate, with the chatbot re-platformed onto Bedrock.

Scope: high-level architecture only. Audience: small shared team, low concurrency.

Decisions locked in (2026-08-15)

This is explicitly a pilot — proving out the research-assistant model on one real domain before any wider rollout or rebrand decision.

These replace the corresponding rows in §9's open-questions list, which is now shorter.

1. Current state

The viewer is a dependency-light Python http.server process (no framework) that renders ~60 Markdown knowledge-graph nodes as linked HTML, serves a handful of source PDFs, renders timestamped video transcripts from saved caption files, and hosts a chatbot backed directly by the Anthropic API with a hand-built, read-only tool set (the knowledge graph, a local EGW Writings database, and a third-party Bible/lexicon MCP server). It runs on one machine, reads one SQLite file from local disk, and authenticates the chatbot with an API key in a .env file. None of that survives a move to a shared, always-on service as-is.

The number that shapes everything below: data/egw-writings.db is 4.7 GB. Every sizing and persistence decision in this document is downstream of that one fact — it's too large to treat casually, and too large to bake into a container image without consequences.

Also true today, worth stating plainly since it affects the pipeline section: this project is not yet in a git repository. That's step zero, not an afterthought.

2. Proposed architecture

Two Fargate tasks, split across two Availability Zones, behind an Application Load Balancer — the standard shape for a low-traffic internal service that still shouldn't go dark if one AZ has a bad day. Both tasks mount the same read-only data volume; that only works because the content is idempotent (a batch-synced archive, not live user data), which is exactly the case here.


Internet users on the internal team Route 53 DNS ALB (public subnets) HTTPS via ACM cert Availability Zone A — private subnet Availability Zone B — private subnet Fargate task 1 viewer + chatbot container reads /data (read-only) Fargate task 2 viewer + chatbot container reads /data (read-only) EFS — /data SQLite DB · transcripts · PDFs mount, read-only mount, read-only Bedrock (VPC endpoint) invoke model study-bible MCP (external) tool calls, via NAT maintenance task (scheduled) → EFS, read-write, re-syncs content

Figure: two Fargate tasks in separate AZs behind one ALB, both mounting the same EFS volume read-only. A separate scheduled task (not the app) mounts EFS read-write to run the existing catalog-sync.js / zip-sync.js content updates — the running app never writes.

Components

3. Compute sizing

This is a starting point to validate against real CloudWatch numbers after the first deploy, not a final answer — but it needs to be some concrete number to review, so:

Task vCPU Memory Why
Starting size (recommended) 0.5 vCPU 1 GB The app is I/O-bound, not CPU-bound — it's mostly waiting on SQLite reads over EFS and on Bedrock/MCP round-trips, not computing. 1 GB covers the Python process, request handling, and SQLite's page cache for the working set of a given query; it does not need to hold the 4.7 GB file in memory.
If it's sluggish 1 vCPU 2 GB Next step up if Container Insights shows CPU throttling or memory pressure under real use.

Two tasks at the starting size is a deliberately small, cheap baseline for "a small team, low traffic" — see §8 for what that costs. Scale-out policy: keep it simple to start (a fixed desired count of 2), add ECS service auto-scaling on CPU or ALB request count only if usage patterns actually call for it later.

4. Data & persistence

The single architectural decision everything else here hangs on: the 4.7 GB SQLite database does not go into the Docker image.


Option A — bake into image app code + 4.7 GB SQLite baked in image ≈ 5 GB → slow build, slow ECR push, slow pull on every task start/deploy Option B — EFS-mounted (recommended) app code image ≈ tens of MB EFS volume 4.7 GB, mounted at task start mounts → fast deploys; data updates independently, without rebuilding the image

Figure: baking the database in bloats every image build and every task start with a 4.7 GB payload that rarely changes. Mounting it from EFS keeps the two concerns — "deploy new code" and "update the archive" — independent, which matches how this project already operates (content synced by separate Node scripts, on its own schedule).

Approach Deploy speed Update story Verdict
Bake into image Slow — multi-GB image every build Every content update requires a full rebuild + redeploy Avoid
EFS mount Fast — image stays small Separate scheduled task re-syncs EFS directly; no redeploy needed Recommended
Download from S3 on boot Slow task start (downloads 4.7 GB per task launch) Straightforward, but pays the download cost on every scale-out/restart Fallback only

Updating content

A scheduled task (EventBridge Scheduler → one-off Fargate task, or just run manually) mounts the same EFS volume read-write and runs the existing catalog-sync.js / zip-sync.js Node scripts against it. The two always-on app tasks mount read-only and never touch the sync process — clean separation, and consistent with "content is idempotent enough" as stated: readers never race a writer.

4a. Source PDFs — decided 2026-08-15

The four PDFs at the repo root exist locally purely to let the research assistant analyze them without traversing the internet on every query — not because they need to be served by this deployment. Checked each one for a legitimate public source instead of hosting local copies:

PDF Public source Verdict
Rodriguez, Tithing in the Writings of Ellen White Biblical Research Institute — the publisher's own site Link out, don't host
Ferrell, The Broken Blueprint archive.org (PDF/EPUB/full-text) Link out, don't host
Hawley, The Second Advent Doctrine Vindicated (1843) archive.org — public domain Link out, don't host
Standish & Standish, Tithes and Offerings: Trampling the Conscience None found — commercially sold (Hartland Publications, 1997, ISBN 9780923309534), still in print via Amazon/AbeBooks Keep local-only; do not serve or link publicly

Three of four have a legitimate free public source and don't need to live on EFS at all in this deployment — the viewer's PDF-listing feature (/pdf/<filename> in viewer/server.py) should point those three at their public URLs instead. The fourth is still commercially sold with no free public edition; serving it from a public-facing app would be a real copyright problem, not just an access- control nicety. It stays exactly what it always was — a private local research aid for the agent, excluded from whatever goes on EFS/the public site.

5. Chatbot on Bedrock

The research-assistant's tool-use loop (assistant.py) stays structurally the same — the change is entirely in how it talks to the model.

Today Proposed
Client anthropic.Anthropic(api_key=...) anthropic.AnthropicBedrock(...) — same SDK, same .messages.create / .messages.stream surface used today, so the tool-use loop and streaming logic don't need a rewrite.
Auth API key in .env ECS task IAM role — no secret to store or rotate at all. This removes the one credential this whole system currently has.
Model selection Model name string Bedrock model ID (region-qualified) — confirm the exact current ID for the intended Claude model in the Bedrock console at implementation time rather than assuming one here.

IAM: the task role needs bedrock:InvokeModel and bedrock:InvokeModelWithResponseStream, scoped to the specific model ARN(s) in use — not a wildcard.

Doesn't change: the study-bible MCP dependency stays external either way — it's a third-party hosted service, not an AWS resource, so moving the chatbot to Bedrock doesn't bring it in-house. See the open questions in §9 for whether that's worth revisiting later.

6. CI/CD pipeline

Shape of the pipeline, not a finished implementation:

  1. Step zero: push this repository to GitHub (decided — see Decisions above).
  2. Build: on push, build the application image (small — see §4) and push to ECR via GitHub Actions with OIDC-based AWS auth — no long-lived AWS credentials stored in GitHub, the workflow assumes an IAM role scoped to this pipeline for the duration of the run.
  3. Deploy: update the ECS service to the new image, rolling deployment across both AZs (one task at a time, so the service never drops to zero capacity). The VPC, ECS service, ALB, and EFS volume themselves are defined in AWS CDK (decided) — the same GitHub Actions workflow can run cdk deploy for infrastructure changes, separately from the plain image build/deploy for code changes.
  4. Content updates run on their own, separate track (§4's scheduled sync task) — decoupled from the code pipeline entirely, since data lives on EFS, not in the image.

Not yet started: the CDK app itself, and the Dockerfile.

7. Security posture

8. Rough cost shape

Order-of-magnitude only — confirm against current AWS pricing for the target region before this becomes a real budget line:

Total shape: a small, mostly-fixed monthly infrastructure floor, plus Bedrock usage that scales with how much the chatbot actually gets used.

9. Open questions

Source control, AWS account, domain, IaC tool, and the source-PDF handling below are now decided (see Decisions above and §4a). Still open:

10. Next steps

  1. Push this repository to GitHub.
  2. Write a Dockerfile for the viewer; confirm it runs locally exactly as it does today, reading a local EFS-shaped mount point.
  3. Stand up the VPC, ECS service, ALB, and EFS volume as a CDK app.
  4. Migrate assistant.py to AnthropicBedrock; drop the .env/API-key path entirely.
  5. Wire the CI/CD pipeline (§6) and the separate scheduled content-sync task (§4).
  6. Deploy, smoke-test both AZs independently (kill one task, confirm the ALB routes around it), and watch Container Insights for a week before revisiting the sizing guess in §3.