Skip to content

Commit 0afbad0

Browse files
committed
Optimize deployment flow with GHCR and pre-built images
- Build Docker images once in GitHub Actions, not on EC2 - Push multi-arch images (amd64/arm64) to GitHub Container Registry - Reduce deployment time from 5-10 minutes to <30 seconds - Minimize S3 usage to config files only (~few KB vs several MB) - Update docker-compose.prod.yml to pull from GHCR instead of building - Simplify CodeDeploy scripts to pull pre-built images - Keep S3 bucket for minimal config delivery (appspec.yml, scripts) - Update documentation with optimized deployment flow
1 parent 1a8d0fd commit 0afbad0

6 files changed

Lines changed: 115 additions & 35 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@ on:
77

88
env:
99
AWS_REGION: ${{ vars.AWS_REGION || 'us-east-1' }}
10+
REGISTRY: ghcr.io
11+
IMAGE_NAME: ${{ github.repository }}
1012

1113
permissions:
1214
contents: write # Need write permission for creating releases
15+
packages: write # Need write permission for GHCR
1316

1417
jobs:
1518
test:
@@ -114,10 +117,58 @@ jobs:
114117
prerelease: false
115118
generate_release_notes: false
116119

120+
build-and-push:
121+
name: Build and Push Docker Image
122+
runs-on: ubuntu-latest
123+
needs: release
124+
125+
steps:
126+
- name: Checkout code
127+
uses: actions/checkout@v4
128+
129+
- name: Extract version from tag
130+
id: version
131+
run: |
132+
VERSION=${GITHUB_REF#refs/tags/v}
133+
echo "version=$VERSION" >> $GITHUB_OUTPUT
134+
echo "Version: $VERSION"
135+
136+
- name: Set up Docker Buildx
137+
uses: docker/setup-buildx-action@v3
138+
139+
- name: Log in to GitHub Container Registry
140+
uses: docker/login-action@v3
141+
with:
142+
registry: ${{ env.REGISTRY }}
143+
username: ${{ github.actor }}
144+
password: ${{ secrets.GITHUB_TOKEN }}
145+
146+
- name: Extract metadata for Docker
147+
id: meta
148+
uses: docker/metadata-action@v5
149+
with:
150+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
151+
tags: |
152+
type=semver,pattern={{version}}
153+
type=semver,pattern={{major}}.{{minor}}
154+
type=semver,pattern={{major}}
155+
type=raw,value=latest
156+
157+
- name: Build and push Docker image
158+
uses: docker/build-push-action@v6
159+
with:
160+
context: .
161+
platforms: linux/amd64,linux/arm64
162+
push: true
163+
tags: ${{ steps.meta.outputs.tags }}
164+
labels: ${{ steps.meta.outputs.labels }}
165+
cache-from: type=gha
166+
cache-to: type=gha,mode=max
167+
117168
deploy:
118169
name: Deploy to AWS EC2 via CodeDeploy
119170
runs-on: ubuntu-latest
120-
needs: release
171+
needs: build-and-push
121172

122173
steps:
123174
- name: Checkout code
@@ -139,16 +190,17 @@ jobs:
139190

140191
- name: Create deployment package
141192
run: |
142-
echo "📦 Creating deployment package..."
143-
144-
# Create archive excluding unnecessary files
145-
zip -r deployment-${{ steps.version.outputs.version }}.zip . \
146-
-x "*.git*" \
147-
-x "*.terraform*" \
148-
-x "pb_data/*" \
149-
-x "tmp/*" \
150-
-x ".air.toml" \
151-
-x "*.log"
193+
echo "📦 Creating minimal deployment package..."
194+
195+
# Only include CodeDeploy scripts and docker-compose
196+
mkdir -p deploy-package
197+
cp -r scripts deploy-package/
198+
cp appspec.yml deploy-package/
199+
cp docker-compose.prod.yml deploy-package/
200+
201+
cd deploy-package
202+
zip -r ../deployment-${{ steps.version.outputs.version }}.zip .
203+
cd ..
152204
153205
echo "✅ Deployment package created"
154206

DEPLOY.md

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,29 @@ Elastic IP (static)
1212
|
1313
1414
EC2 Instance (t4g.micro - Amazon Linux 2023 ARM64)
15-
├── CodeDeploy Agent (automated deployment)
15+
├── CodeDeploy Agent (pulls config from S3)
1616
├── Traefik (Port 80/443)
1717
│ ├── Let's Encrypt SSL
1818
│ └── Auto HTTPS redirect
19-
└── Planning Poker Container
19+
└── Planning Poker Container (from GHCR)
2020
└── /app/pb_data → /mnt/data (EBS Volume)
2121
```
2222

23+
**Deployment Flow:**
24+
```
25+
GitHub Actions (on tag push)
26+
├─→ Build Docker image → Push to GHCR (pre-built, multi-arch)
27+
└─→ Upload config package to S3 → Trigger CodeDeploy
28+
└─→ EC2 pulls image from GHCR → Run
29+
```
30+
2331
**Key Features:**
24-
-**Automated Deployment**: AWS CodeDeploy with GitHub Actions
32+
-**Optimized Deployment**: Build once in GitHub, deploy pre-built image
33+
-**Fast Deploys**: No compilation on EC2, just pull and run (<30 seconds)
34+
-**Container Registry**: GitHub Container Registry (GHCR) for Docker images
35+
-**Minimal S3 Usage**: Only config files (~few KB), not application code
2536
-**Persistent Storage**: EBS volume for database (survives container restarts)
2637
-**Automatic SSL**: Traefik handles Let's Encrypt certificates
27-
-**Simple**: Single EC2 instance with Docker Compose
2838
-**Cost-Effective**: t4g.micro eligible for AWS free tier
2939

3040
## Prerequisites
@@ -70,13 +80,15 @@ This creates S3 buckets (Terraform state + CodeDeploy), IAM user, and policies:
7080
The script will:
7181
- Create IAM user for GitHub Actions
7282
- Create S3 bucket for Terraform state (with versioning and encryption)
73-
- Create S3 bucket for CodeDeploy deployment packages
74-
- Configure IAM policies (Terraform, CodeDeploy, S3)
83+
- Create S3 bucket for CodeDeploy config packages (minimal size - appspec.yml, docker-compose, scripts only)
84+
- Configure IAM policies (Terraform, CodeDeploy, S3, GHCR)
7585
- Generate access keys
7686
- Output configuration for GitHub secrets
7787

7888
**Important**: Save the output - it contains bucket names and credentials.
7989

90+
**Note**: The S3 bucket for CodeDeploy now only stores small config files (~few KB), not the entire application code. Docker images are built in GitHub Actions and stored in GitHub Container Registry.
91+
8092
### 3. Configure Terraform Backend
8193

8294
```bash
@@ -233,12 +245,14 @@ git push origin v0.1.11
233245
This will:
234246
1. Run tests in GitHub Actions
235247
2. Create a GitHub release with changelog
236-
3. Package application and upload to S3
237-
4. Create CodeDeploy deployment
238-
5. CodeDeploy will:
248+
3. Build multi-arch Docker image (amd64/arm64) and push to GHCR
249+
4. Package config files (appspec.yml, docker-compose.prod.yml, scripts) and upload to S3
250+
5. Create CodeDeploy deployment
251+
6. CodeDeploy will:
239252
- Stop running containers
240-
- Deploy new code
241-
- Build and start new containers
253+
- Deploy new config files
254+
- Pull latest image from GHCR
255+
- Start new containers
242256
- Validate service health
243257

244258
Monitor deployment:
@@ -255,17 +269,18 @@ ssh ec2-user@$(terraform output -raw instance_public_ip)
255269
# Navigate to app directory
256270
cd /opt/planning-poker
257271

258-
# Pull latest changes
259-
sudo git pull origin main # or git checkout v1.0.0
272+
# Pull latest image from GHCR
273+
sudo docker compose -f docker-compose.prod.yml pull app
260274

261-
# Rebuild and restart manually
262-
sudo docker compose -f docker-compose.prod.yml build --no-cache
275+
# Restart with new image
263276
sudo docker compose -f docker-compose.prod.yml up -d
264277

265278
# View logs
266-
sudo docker compose -f docker-compose.prod.yml logs -f
279+
sudo docker compose -f docker-compose.prod.yml logs -f app
267280
```
268281

282+
**Note**: The application is no longer built on the EC2 instance. Images are pre-built in GitHub Actions and stored in GitHub Container Registry (GHCR).
283+
269284
## Persistence & Backups
270285

271286
**Database Location:**

docker-compose.prod.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ services:
2525
- app
2626

2727
app:
28-
build: .
28+
image: ghcr.io/damione1/planning-poker:latest
2929
container_name: planning-poker
3030
restart: unless-stopped
31+
pull_policy: always
3132
volumes:
3233
- /mnt/data/pb_data:/app/pb_data
3334
environment:

scripts/codedeploy/start_application.sh

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,21 @@ echo "=== Starting application ==="
55

66
cd /opt/planning-poker
77

8-
# Load environment variables from Terraform (if available)
8+
# Load and export environment variables
99
if [ -f /etc/environment ]; then
10+
set -a # automatically export all variables
1011
source /etc/environment
12+
set +a
1113
fi
1214

13-
# Build the application image
14-
echo "Building Docker image..."
15-
docker compose -f docker-compose.prod.yml build --no-cache app
15+
# Verify environment variables are set
16+
echo "Environment check:"
17+
echo " DOMAIN_NAME: ${DOMAIN_NAME:-NOT SET}"
18+
echo " LETS_ENCRYPT_EMAIL: ${LETS_ENCRYPT_EMAIL:-NOT SET}"
19+
20+
# Pull latest image from GHCR
21+
echo "Pulling latest image from GitHub Container Registry..."
22+
docker compose -f docker-compose.prod.yml pull app
1623

1724
# Start all services
1825
echo "Starting services..."

terraform/main.tf

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ resource "random_id" "codedeploy_bucket" {
286286
byte_length = 4
287287
}
288288

289-
# S3 Bucket for CodeDeploy deployment packages
289+
# S3 Bucket for CodeDeploy deployment packages (configs only, not application code)
290290
resource "aws_s3_bucket" "codedeploy" {
291291
bucket = "${var.service_name}-codedeploy-${random_id.codedeploy_bucket.hex}"
292292

@@ -339,7 +339,7 @@ data "aws_iam_user" "github_actions" {
339339
user_name = var.github_actions_user
340340
}
341341

342-
# IAM policy for GitHub Actions to upload to CodeDeploy bucket
342+
# IAM policy for GitHub Actions to upload configs and trigger CodeDeploy
343343
resource "aws_iam_user_policy" "github_actions_codedeploy" {
344344
name = "${var.service_name}-github-actions-codedeploy-policy"
345345
user = data.aws_iam_user.github_actions.user_name

terraform/outputs.tf

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ output "backup_plan" {
8888
}
8989

9090
output "codedeploy_s3_bucket" {
91-
description = "S3 bucket for CodeDeploy deployment packages"
91+
description = "S3 bucket for CodeDeploy config packages (minimal size)"
9292
value = aws_s3_bucket.codedeploy.bucket
9393
}
94+
95+
output "container_registry" {
96+
description = "Container registry for Docker images"
97+
value = "ghcr.io/${var.github_repo}"
98+
}

0 commit comments

Comments
 (0)