🚀 Chapter 2 — Automation: Phần A — Thiết lập CD với AWS CodeBuild + SSH
Ở Chapter 1, chúng ta đã deploy Todo List App lên Amazon EC2 bằng cách thủ công:
Developer
↓
SSH vào EC2
↓
git pull
↓
docker compose build
↓
docker compose up -d
Cách này hoạt động, nhưng mỗi lần có code mới chúng ta lại phải SSH vào server và chạy các command trên.
Trong Chapter này, chúng ta sẽ tự động hóa quá trình đó bằng:
- GitHub
- AWS CodeBuild
- AWS Secrets Manager
- SSH
- Amazon EC2
Mục tiêu cuối cùng:

Không sử dụng GitHub Actions.
1. Chúng ta sẽ xây dựng gì?
Ở Chapter 1:
Developer
↓
SSH
↓
EC2
↓
Deploy thủ công
Sau Chapter này:
Developer
↓
git push
↓
GitHub
↓
CodeBuild
↓
SSH
↓
EC2
↓
Deploy tự động
Điểm quan trọng:
CodeBuild không trực tiếp chạy Docker Compose trên EC2.
CodeBuild chỉ đóng vai trò:
- Nhận source/event.
- Lấy SSH private key.
- SSH vào EC2.
- Ra lệnh cho EC2 deploy.
Còn việc:
git pull
docker compose build
docker compose up -d
vẫn được thực hiện trên EC2.
2. Kiến trúc
Kiến trúc của Chapter này:

3. Chuẩn bị EC2
Chúng ta tiếp tục sử dụng EC2 từ Chapter 1.
Kiểm tra:
ssh -i ./chapter1-key.pem ubuntu@<EC2_PUBLIC_IP>
Sau đó:
docker --version
docker compose version
git --version
Đi tới project:
cd ~/todo-list-app
Kiểm tra container:
docker compose ps
Nếu application đang chạy:
NAME STATUS
todo-list-app-app-1 Up
todo-list-app-db-1 Up
thì có thể tiếp tục.
Nên Tạo Github repo mới cho todo-app
Ở chapter 1, chúng ta đang sử dụng repo mã nguồn mở của người khác. Từ chương này, nên tạo một repo mới của riêng mình để có quyền kiểm soát với repo.
Ví dụ như:
- Push code
- Merge PR
Vì CI/CD sẽ được thực hiện tự động bởi action push code, merge PR hoặc điều gì đó tương tự
Nên dùng Elastic IP cho EC2
CodeBuild sẽ SSH vào EC2.
Nếu EC2 sử dụng Public IPv4 thông thường, IP có thể thay đổi khi instance được stop/start.
Vì vậy, cho lab này nên sử dụng:
EC2
↓
Elastic IP
Trong AWS Console:
EC2
↓
Elastic IP addresses
↓
Allocate Elastic IP address
↓
Associate Elastic IP address
↓
Chọn EC2
Sau khi có Elastic IP, chúng ta có:
EC2_PUBLIC_IP=<ELASTIC_IP>
Ví dụ:
EC2_PUBLIC_IP=13.XXX.XXX.XXX
Từ đây trở đi sử dụng Elastic IP này.
Kiểm tra Security Group
CodeBuild sẽ SSH vào EC2 nên Security Group của EC2 cần cho phép SSH:
Type: SSH
Protocol: TCP
Port: 22
Để làm lab đơn giản, có thể:
Source: 0.0.0.0/0
Tuy nhiên, đây chỉ là cấu hình đơn giản cho lab.
Không cần mở:
3000
3306
Application vẫn truy cập thông qua:
HTTP : 80
4. Lưu SSH private key vào Secrets Manager
CodeBuild chạy trong AWS nên nó không có file:
chapter1-key.pem
trên máy local của chúng ta.
Vì vậy CodeBuild cần lấy private key từ AWS Secrets Manager.
Vào:
AWS Console
↓
Secrets Manager
↓
Store a new secret
Chọn:
Other type of secret
Quan trọng
Để đơn giản và tránh lỗi parse JSON, hãy lưu toàn bộ private key dưới dạng plaintext.
Ví dụ:
-----BEGIN OPENSSH PRIVATE KEY-----
...
...
-----END OPENSSH PRIVATE KEY-----
Đặt tên:
chapter2/ec2-ssh-key
Sau đó lưu secret.
Không commit file
.pemvào Git repository.
5. Chuẩn bị trước khi tạo CodeBuild
Tạo IAM Role cho CodeBuild
CodeBuild cần quyền đọc secret.
Vào:
IAM
↓
Roles
↓
Create role
Chọn:
Trusted entity:
AWS service
Use case:
CodeBuild
Đặt tên:
chapter2-codebuild-role
Sau khi tạo role, thêm Inline Policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue"
],
"Resource": "arn:aws:secretsmanager:us-east-1:<AWS_ACCOUNT_ID>:secret:chapter2/ec2-ssh-key-*"
},
{
"Effect": "Allow",
"Action": [
"codeconnections:UseConnection"
],
"Resource": "arn:aws:codeconnections:us-east-1:<AWS_ACCOUNT_ID>:connection/<CONNECTION_ID>"
}
]
}
Thay:
<AWS_ACCOUNT_ID>
bằng AWS Account ID của bạn.
Policy này có nghĩa:
CodeBuild ──────────────→ GitHub connection
↓
GetSecretValue
↓
chapter2/ec2-ssh-key
CodeBuild chỉ được đọc secret cần thiết.
Chuẩn bị repository
Trong repository:
todo-list-app/
├── buildspec.yml (new)
├── compose.yaml
├── Dockerfile
├── package.json
└── ...
Tạo file:
buildspec.yml
CodeBuild mặc định tìm buildspec.yml ở root của source repository.
Tạo buildspec.yml
Ban đầu chúng ta chưa deploy.
Chỉ kiểm tra:
CodeBuild
↓
SSH
↓
EC2
Nội dung:
version: 0.2
phases:
install:
commands:
- yum install -y openssh-clients
- mkdir -p ~/.ssh
- aws secretsmanager get-secret-value --secret-id chapter2/ec2-ssh-key --query SecretString --output text > /tmp/ec2-key.pem
- chmod 400 /tmp/ec2-key.pem
build:
commands:
- echo "Preparing deployment..."
post_build:
commands:
- echo "Deploying to EC2..."
- ssh-keyscan -H <EC2_PUBLIC_IP> >> ~/.ssh/known_hosts
- ssh -i /tmp/ec2-key.pem ubuntu@<EC2_PUBLIC_IP> "echo SSH connection successful"
Thay:
<EC2_PUBLIC_IP>
bằng Elastic IP của EC2.
Ví dụ:
- ssh-keyscan -H 13.XXX.XXX.XXX >> ~/.ssh/known_hosts
- ssh -i /tmp/ec2-key.pem ubuntu@13.XXX.XXX.XXX "echo SSH connection successful"
Commit buildspec.yml
Ở máy local:
git add buildspec.yml
git commit -m "add CodeBuild deployment"
git push origin main
6. Tạo CodeBuild Project
Vào:
AWS Console
↓
CodeBuild
↓
Build projects
↓
Create build project
Đặt:
Project name:
chapter2-todo-list-cd
Source
Chọn:
Source provider:
GitHub
Sau đó kết nối GitHub repository chứa:
todo-list-app
Branch:
main
CodeBuild hỗ trợ GitHub làm source provider và có thể nhận webhook từ repository.
Cấu hình Environment
Chọn:
Environment image:
Managed image
Operating system:
Amazon Linux
Runtime:
Standard
Chọn một image Standard hiện có trong Console.
Ví dụ:
aws/codebuild/amazonlinux-x86_64-standard:...
Service role:
chapter2-codebuild-role
Privileged mode
Không bật Privileged mode.
Trong Chapter này CodeBuild không build Docker image.
Docker được build trên EC2 thông qua SSH:
CodeBuild
↓
SSH
↓
EC2
↓
docker compose build
AWS chỉ cần privileged mode khi build project cần Docker daemon bên trong CodeBuild environment.
Cấu hình Buildspec
Trong phần Buildspec:
Use a buildspec file
Không cần nhập tên nếu file nằm ở root và có tên:
buildspec.yml
Artifacts:
No artifacts
Vì Chapter này chỉ thực hiện deployment.
Logs:
CloudWatch Logs
Sau đó:
Create build project
7. Chạy CodeBuild lần đầu
Vào:
CodeBuild
↓
Build projects
↓
chapter2-todo-list-cd
↓
Start build
CodeBuild sẽ thực hiện:
INSTALL
↓
Install SSH client
↓
Get SSH private key
↓
BUILD
↓
POST_BUILD
↓
SSH → EC2
Nếu thành công, log sẽ có:
SSH connection successful
8. Nếu SSH thành công thì chúng ta đã hoàn thành một nửa
Bây giờ flow đã hoạt động:
CodeBuild
│
│ SSH
▼
Amazon EC2
Điều này chứng minh:
- IAM Role hoạt động.
- CodeBuild đọc được Secrets Manager.
- SSH key hợp lệ.
- Security Group cho phép SSH.
- CodeBuild có thể kết nối tới EC2.
Chúng ta chưa deploy application.
Đây là cách debug dễ hơn so với việc đưa tất cả command vào ngay từ đầu.
9. Thêm bước Deploy
Bây giờ sửa buildspec.yml.
Thay phần:
post_build:
commands:
- ssh-keyscan -H <EC2_PUBLIC_IP> >> ~/.ssh/known_hosts
- ssh -i /tmp/ec2-key.pem ubuntu@<EC2_PUBLIC_IP> "echo SSH connection successful"
bằng:
post_build:
commands:
- echo "Deploying to EC2..."
- ssh-keyscan -H <EC2_PUBLIC_IP> >> ~/.ssh/known_hosts
- >
ssh -i /tmp/ec2-key.pem
ubuntu@<EC2_PUBLIC_IP>
"cd ~/todo-list-app &&
git pull origin main &&
docker compose build &&
docker compose up -d"
buildspec.yml hoàn chỉnh
File cuối cùng:
version: 0.2
phases:
install:
commands:
- yum install -y openssh-clients
- mkdir -p ~/.ssh
- aws secretsmanager get-secret-value --secret-id chapter2/ec2-ssh-key --query SecretString --output text > /tmp/ec2-key.pem
- chmod 400 /tmp/ec2-key.pem
build:
commands:
- echo "Preparing deployment..."
post_build:
commands:
- echo "Deploying to EC2..."
- ssh-keyscan -H 34.201.104.212 >> ~/.ssh/known_hosts
- >
ssh -i /tmp/ec2-key.pem
ubuntu@34.201.104.212
"cd ~/todo-list-app &&
git pull origin main &&
docker compose build &&
docker compose up -d"
Thay:
<EC2_PUBLIC_IP>
bằng Elastic IP thật.
Một điều kiện quan trọng: EC2 phải pull được GitHub
Trong deployment command có:
git pull origin main
Điều này có nghĩa EC2 cũng phải có quyền đọc repository.
Nếu repository là public, không cần cấu hình thêm.
Nếu repository là private, EC2 cần được cấu hình GitHub authentication, ví dụ:
GitHub Deploy Key
hoặc một cơ chế authentication phù hợp.
Trong lab này, để giữ kiến trúc đơn giản, có thể sử dụng repository public.
Commit và push
Sau khi sửa:
git add buildspec.yml
git commit -m "deploy todo app through CodeBuild"
git push origin main
Hiện tại chúng ta vẫn có thể chạy thủ công:
CodeBuild
↓
Start build
Chạy deployment
Trong CodeBuild:
chapter2-todo-list-cd
↓
Start build
Theo dõi log.
Flow sẽ là:
INSTALL
↓
Install SSH
↓
Get SSH key
↓
BUILD
↓
POST_BUILD
↓
SSH → EC2
↓
git pull
↓
docker compose build
↓
docker compose up -d
Nếu thành công:
BUILD SUCCEEDED
10. Xác nhận kết quả
Kiểm tra EC2
SSH vào EC2:
ssh -i ./chapter1-key.pem ubuntu@<EC2_PUBLIC_IP>
Đi tới project:
cd ~/todo-list-app
Kiểm tra commit:
git log -1 --oneline
Kiểm tra container:
docker compose ps
Kiểm tra image:
docker images
Nếu mọi thứ đúng, container mới đã được deploy.
Kiểm tra application
Mở:
http://<EC2_PUBLIC_IP>
Todo List App phải hoạt động.
11. Tự động trigger CodeBuild
Hiện tại chúng ta vẫn phải:
git push
↓
AWS Console
↓
Start build (thủ công)
Mục tiêu của CD là bỏ bước:
Start build
CodeBuild hỗ trợ webhook để tự động bắt đầu build khi source repository có thay đổi.
Vào:
CodeBuild
↓
chapter2-todo-list-cd
↓
Edit
Thiết lập webhook như ảnh:

Ý nghĩa:
Hệ thống sẽ tự động khởi chạy một quy trình build mới mỗi khi có bất kỳ thay đổi mã nguồn nào (commit/push) được đẩy lên repository
Test Auto Deployment
Bây giờ chúng ta chỉ cần:
git add .
git commit -m "update todo app"
git push origin main
Không cần:
❌ GitHub Actions
❌ AWS Console → Start build
❌ SSH vào EC2
❌ git pull
❌ docker compose build
❌ docker compose up
Kiểm tra Build History
Vào:
CodeBuild
↓
chapter2-todo-list-cd
↓
Build history
Bạn sẽ thấy build mới.
Ví dụ:

Sau đó kiểm tra EC2:
cd ~/todo-list-app
git log -1 --oneline
docker compose ps
Cuối cùng mở:
http://<EC2_PUBLIC_IP>
12. Kết quả cuối cùng

Điểm quan trọng nhất của Chapter này:
CodeBuild = Automation engine
EC2 = nơi application thực sự được deploy
Secrets Manager = nơi lưu SSH private key
GitHub = source repository + webhook
13. Kiến trúc này còn hạn chế gì?
Cách làm này rất phù hợp để học CD cơ bản, nhưng chưa phải kiến trúc production hoàn chỉnh.
Hiện tại chúng ta vẫn:
CodeBuild
↓
SSH
↓
EC2
và phải quản lý:
SSH private key
SSH port 22
EC2 Public IP
EC2 GitHub access
Ngoài ra, mỗi lần deploy EC2 vẫn phải:
git pull
docker compose build
Điều này có nghĩa server tự build application.
Ở các phần tiếp theo, chúng ta sẽ dần chuyển sang mô hình:
Source Code
↓
Build
↓
Docker Image
↓
Container Registry
↓
Deploy
Ví dụ:
GitHub
↓
CodeBuild
↓
Docker Build
↓
Amazon ECR
↓
EC2 / EKS
Khi đó chúng ta không cần để production server tự build Docker image nữa.
14. Chapter tiếp theo
Sau khi đã có:
Git push
↓
CodeBuild
↓
EC2
↓
Docker Compose
chúng ta có thể bắt đầu giải quyết vấn đề tiếp theo:
Làm thế nào để cùng một quy trình CI/CD có thể deploy nhiều service?
Đó chính là bước chuyển từ:
1 application
sang:
Multiple services
và bắt đầu bước vào:
Chapter 3 — Scale
🎯 Kết quả của Chapter 2A
Cuối bài, bạn cần đạt được:
[✓] EC2 chạy Todo List App
[✓] SSH private key được lưu trong Secrets Manager
[✓] CodeBuild có IAM Role
[✓] CodeBuild SSH được vào EC2
[✓] CodeBuild deploy được application
[✓] GitHub webhook trigger CodeBuild
[✓] git push → application tự động deploy
Nếu đạt đủ 7 mục trên, bạn đã hoàn thành CD cơ bản với AWS CodeBuild + SSH.
All rights reserved