Automated daily synchronization from Notion to a SvelteKit static blog using AWS ECS Fargate and EventBridge.
Notion API → ECS Fargate (Daily at Midnight KST)
↓
1. Fetch pages (Go)
2. Download images (Go)
3. Generate .svelte files (Go)
↓
4. Git sync (Bash script)
- Clone repo
- Copy files
- Push to 'posts' branch
↓
GitHub Actions (auto-triggered)
- npm ci
- npm run build
- Deploy to GitHub Pages
↓
Blog updated! ✨
master: Development (code changes, PRs, infrastructure)posts: Production (blog posts only, triggers deployment)~$0.10/month 💰
repo scopeThis project uses GitHub Pages for blog hosting with User/Organization site format:
<username>.github.iohttps://<username>.github.io/johndoe, create repository johndoe.github.io# On GitHub.com, create a new PUBLIC repository named:
# <your-username>.github.io
# Clone this template
git clone https://github.com/jhseoeo/notion-blog.git
cd notion-blog
# Update remote to your repository
git remote set-url origin https://github.com/<your-username>/<your-username>.github.io.git
git push -u origin master
Edit terraform/terraform.tfvars:
project_name = "notion-blog-sync"
aws_region = "ap-northeast-2"
# Get from Notion Integration page
notion_secret = "ntn_xxxxxxxxxxxxx"
notion_db_id = "your-database-id"
# Create GitHub PAT with 'repo' scope
github_token = "ghp_xxxxxxxxxxxxx"
github_repo = "https://github.com/<YOUR_USERNAME>/<YOUR_USERNAME>.github.io.git"
git_user_name = "Notion Blog Sync Bot"
git_user_email = "your-email@example.com"
# Daily at midnight KST (15:00 UTC)
schedule_expression = "cron(0 15 * * ? *)"
Important: Never commit terraform.tfvars to git (already in .gitignore)
Note: The posts branch will be automatically created by the workflow when it runs for the first time. You don’t need to create it manually.
cd terraform
# Initialize Terraform
terraform init
# Review planned changes
terraform plan
# Deploy infrastructure (type 'yes' when prompted)
terraform apply
# Save outputs for next steps
terraform output ecr_repository_url
gh-pages/ (root)Your blog will be available at: https://<your-username>.github.io/
Note: The gh-pages branch will be created automatically by the GitHub Actions workflow when you push to the posts branch.
cd ../workflow
# Get ECR repository URL from terraform output
ECR_REPO=$(cd ../terraform && terraform output -raw ecr_repository_url)
# Login to ECR
aws ecr get-login-password --region ap-northeast-2 | \
docker login --username AWS --password-stdin $ECR_REPO
# Build Docker image
docker build -t notion-blog-sync .
# Tag and push
docker tag notion-blog-sync:latest $ECR_REPO:latest
docker push $ECR_REPO:latest
# Trigger ECS task manually
cd ../terraform
aws ecs run-task \
--cluster notion-blog-sync-cluster \
--task-definition notion-blog-sync \
--launch-type FARGATE \
--network-configuration "awsvpcConfiguration={subnets=[$(terraform output -json subnet_ids | jq -r '.[0]')],securityGroups=[$(terraform output -raw security_group_id)],assignPublicIp=ENABLED}" \
--region ap-northeast-2
# Watch logs
aws logs tail /ecs/notion-blog-sync --follow
Purpose: Automatically rebuild Docker image when workflow code changes
Two workflows are already configured:
deploy-workflow.yml - Rebuilds Docker image on master branch
workflow/** on masterdeploy-pages.yml - Deploys blog on posts branch
posts branchSetup:
AWS_ROLE_ARN - for OIDC authenticationAWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY# Create OIDC provider and IAM role in AWS
# See: https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services
.github/workflows/deploy-workflow.yml):
Now:
master with workflow changes → Docker image rebuildsposts → Blog deploys automatically# Tail logs in real-time
aws logs tail /ecs/notion-blog-sync --follow
# Filter errors
aws logs filter-log-events \
--log-group-name /ecs/notion-blog-sync \
--filter-pattern "ERROR"
# List scheduled tasks
aws events list-rules --name-prefix notion-blog-sync
# View rule details
aws events describe-rule --name notion-blog-sync-daily
# List recent tasks
aws ecs list-tasks --cluster notion-blog-sync-cluster
# Describe specific task
aws ecs describe-tasks \
--cluster notion-blog-sync-cluster \
--tasks <task-id>
Or use AWS Console:
cd workflow
cp .env.example .env # Edit with your credentials
go run main.go
repo scopeterraform.tfvarsThis is normal if Notion content hasn’t changed. Check logs:
No changes to commit
gh-pages / (root)deploy-pages.yml workflowCheck browser console for 404 errors on assets:
notion-blog/
├── .github/
│ └── workflows/
│ ├── deploy-workflow.yml # Docker build on master
│ └── deploy-pages.yml # Blog deploy on posts
├── workflow/ # Go application
│ ├── main.go # Entry point (Notion sync only)
│ ├── sync.sh # Git sync script
│ ├── Dockerfile # Container definition
│ ├── notion/ # Notion API client
│ ├── post/ # Post export logic
│ └── model/ # Data structures
├── terraform/ # Infrastructure as Code
│ ├── vpc.tf # VPC & subnets
│ ├── ecs.tf # Container orchestration
│ ├── eventbridge.tf # Scheduling
│ ├── secrets.tf # SSM Parameter Store
│ ├── iam.tf # IAM roles & policies
│ └── ...
└── blog/ # SvelteKit blog
└── src/
└── posts/ # Generated posts (on posts branch)
git checkout master
cd workflow
# Make your changes
git add .
git commit -m "Update workflow"
git push
# If GitHub Actions is configured, image will auto-deploy
# Otherwise, rebuild and push manually (see step 6)
git checkout master
cd blog
# Make your changes (components, styles, etc.)
git add .
git commit -m "Update blog design"
git push
# Changes won't trigger deployment (only on posts branch)
git checkout posts
cd blog/src/posts
# Create or edit .svelte files
git add .
git commit -m "Add new post"
git push
# Automatically triggers blog deployment!
git checkout master
cd terraform
# Edit .tf files
terraform plan
terraform apply
Edit terraform/terraform.tfvars:
# Run at 9 AM KST (midnight UTC)
schedule_expression = "cron(0 0 * * ? *)"
# Run twice daily (midnight and noon KST)
# Midnight KST: cron(0 15 * * ? *)
# Noon KST: cron(0 3 * * ? *)
Then apply:
cd terraform
terraform apply
# When you want to sync blog code changes to posts branch
git checkout posts
git merge master
git push
To destroy all infrastructure:
cd terraform
terraform destroy
Warning: This will delete all resources including logs and ECR images.
For issues and questions: