$ cdk deploy --all
Deploy your own Filedrop
Every line of Filedrop is open source under the MIT license at github.com/kunal-kejriwal/filedrop-aws. This page walks through standing up your own copy in your own AWS account. Estimated time: ~20 minutes of active work (plus ~24h if SES needs to leave sandbox and you want to email arbitrary recipients).
Prerequisites
- AWS account with programmatic credentials
(
aws configureor SSO). Filedrop will create resources in your account — you pay for them (pennies at portfolio scale, see the cost note below). - Python 3.12 and pip.
- Node.js 20+ — needed for the AWS CDK v2 CLI (the CDK app itself is in Python, but the CLI ships as a Node package).
- git.
- An email address you can verify in SES. This becomes the sender ("From:") for outgoing emails.
Step 1 — Clone the repo
git clone https://github.com/kunal-kejriwal/filedrop-aws.git
cd filedrop-aws Step 2 — Install dependencies
Filedrop uses a Python virtualenv for the CDK app + Lambda bundling, plus the CDK CLI globally via npm.
python3.12 -m venv .venv
# macOS / Linux
source .venv/bin/activate
# Windows PowerShell
# .venv\Scripts\Activate.ps1
pip install -e ".[dev]"
npm install -g aws-cdk@2 Sanity-check the toolchain:
ruff check .
mypy shared functions infra
pytest tests/unit -v
cdk --version Step 3 — Verify your SES sender identity
Filedrop imports an existing SES identity rather than creating one — so you need to verify your sender address before deploying.
-
Go to the SES console → Verified identities
in your target region (Filedrop defaults to
ap-south-1). -
Click Create identity → Email address
→ enter your sender (e.g.
hello@your-domain.com). - Click the confirmation link SES emails you. The identity's status should flip to Verified.
SES sandbox note: new AWS accounts start with SES in sandbox mode — you can only send email to verified addresses too, and are capped at 200 emails/day. To send to arbitrary recipients, submit a production-access request from the SES console. Approval typically takes 24h. In the meantime, the pipeline still works — the flow doesn't require email delivery to succeed because a status endpoint at
GET /uploads/{id}/statuscan hand back the download link onceprocessflips the row toUPLOADED.
Step 4 — Configure CDK context
Copy the example context file and fill in your details:
cp cdk.context.json.example cdk.context.json Then edit cdk.context.json:
{
"region": "ap-south-1",
"senderEmail": "hello@your-domain.com",
"alarmEmail": "you@your-domain.com"
} senderEmail must match the identity you verified in Step 3.
alarmEmail receives CloudWatch alarms when a DLQ has
messages (optional — you can leave it out and subscribe later from the
SNS console).
Step 5 — Bootstrap CDK (one-time per account/region)
CDK needs a small "toolkit" stack in your account to store deployment assets:
cdk bootstrap aws://YOUR-ACCOUNT-ID/ap-south-1
Replace YOUR-ACCOUNT-ID with your 12-digit AWS account ID
(from aws sts get-caller-identity). This creates the
CDKToolkit stack — do it once and forget about it.
Step 6 — Deploy the stacks
export SENDER_EMAIL=hello@your-domain.com
make deploy
# or, without make:
cdk deploy --all --require-approval never \
--context senderEmail=$SENDER_EMAIL Two stacks land in CloudFormation:
- FiledropCoreStack — S3 bucket, DynamoDB tables, SNS topics, SQS queues + DLQs, EventBridge rule, process/notify/audit Lambdas, CloudWatch alarms.
- FiledropApiStack — HTTP API Gateway, request_upload + get_upload_status Lambdas.
First deploy takes ~10 minutes (CFN provisions ~35 resources). CDK prints the API URL on completion:
Outputs:
FiledropApiStack.ApiUrl = https://xxxxxxxxx.execute-api.ap-south-1.amazonaws.com Step 7 — Test it end-to-end
Grab the outputs, then run the load-test script:
export FILEDROP_API_URL=https://xxxxxxxxx.execute-api.ap-south-1.amazonaws.com
export TEST_EMAIL=you@your-domain.com # must be SES-verified while in sandbox
# Grab the audit table name from the CoreStack outputs
AUDIT_TABLE=$(aws cloudformation describe-stacks \
--stack-name FiledropCoreStack \
--query "Stacks[0].Outputs[?OutputKey=='AuditTableName'].OutputValue" \
--output text)
python scripts/load_test.py \
--count 5 \
--api-url $FILEDROP_API_URL \
--email $TEST_EMAIL \
--audit-table $AUDIT_TABLE If everything is wired correctly you'll see something like:
running 5 uploads against https://xxx.execute-api.ap-south-1.amazonaws.com
uploads complete; polling audit table for up to 120s
results: ok=5/5 p50=1.17 p95=2.10 p99=2.10 Step 8 — Set up GitHub OIDC deploy (optional)
The repo includes a GitHub Actions workflow that redeploys on every
push to main via OIDC — no long-lived access keys.
To wire it up:
-
In IAM → Identity providers, add an OIDC provider
for
https://token.actions.githubusercontent.comwith audiencests.amazonaws.com. -
Create an IAM role with the trust policy in
docs/architecture.md(edit the placeholder account ID and repo path). -
Attach a policy that lets CDK deploy (broad:
PowerUserAccess+IAMFullAccess; tighter: the actual services Filedrop touches). -
Add the role ARN to your GitHub repo secrets as
AWS_DEPLOY_ROLE_ARN, plusSENDER_EMAIL(and optionallyALARM_EMAIL). -
Copy
filedrop-aws/.github-workflows-templates/filedrop-deploy.ymlto.github/workflows/.
Cost sketch
At portfolio-scale traffic (a few dozen uploads a day) Filedrop costs basically nothing — under $1/month, most of it CloudWatch logs. All services are on-demand billed:
- DynamoDB on-demand — pay per request, no minimum
- S3 — 7-day lifecycle on uploads/, so storage is near-zero
- Lambda — free tier covers first 1M invocations/month
- SES — $0.10 per 1,000 emails (62k/month free from EC2/Lambda)
- API Gateway HTTP API — free tier covers first 1M requests
Teardown
When you're done, one command evicts everything:
cdk destroy --all --force
Both stacks are RemovalPolicy.DESTROY, including the S3
bucket (auto_delete_objects=True) and the DynamoDB tables
— no lingering resources. The SES identity survives (Filedrop imports
it, doesn't manage it) — delete it manually from the SES console if
you want.
Where to poke around next
- Read the ADRs — five short design decisions (CDK vs SAM, SES vs SNS, EventBridge vs direct S3 notifications, idempotency, presigned upload strategy).
- The architecture walkthrough — full flow with IAM per Lambda and failure modes.
-
Trigger a poison-file test: bypass the API and PUT a
.exedirectly (using a slot from a successfulPOST /uploads). Watch the process Lambda quarantine it and the audit table capture thefile_rejectedevent. -
Try the DLQ redrive: force a notify failure (e.g. break the SES IAM
temporarily), let messages hit the DLQ, then run
python scripts/dlq_redrive.py --queue filedrop-notify-dlq --dry-run.
Something broken?
Open an issue on GitHub or ping me at hello@kunalships.dev. If a step here doesn't work end-to-end for you, that's a bug I want to fix.