Skip to content

Commit e3f7a36

Browse files
authored
Deploy the Frontend on ECS (#4198)
1 parent 64946aa commit e3f7a36

File tree

12 files changed

+305
-12
lines changed

12 files changed

+305
-12
lines changed

.github/workflows/deploy.yml

Lines changed: 109 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ jobs:
221221
run: terraform validate -no-color
222222

223223
- name: Terraform apply
224-
run: terraform apply -no-color -auto-approve &> /dev/null
224+
run: terraform apply -target module.pretix -target module.pycon_backend -target module.clamav -target module.database -target module.emails -target module.cluster -no-color -auto-approve &> /dev/null
225225
env:
226226
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
227227
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
@@ -247,11 +247,114 @@ jobs:
247247
done
248248
shell: bash
249249

250-
deploy-fe:
251-
runs-on: ubuntu-latest
250+
build-fe:
252251
needs: [wait-aws-update]
252+
runs-on: [self-hosted]
253+
permissions:
254+
packages: write
255+
contents: read
256+
253257
steps:
254-
- name: Trigger hook
255-
if: github.ref == 'refs/heads/main'
258+
- uses: actions/checkout@v4
259+
with:
260+
ref: ${{ github.ref }}
261+
fetch-depth: 0
262+
- name: Configure AWS credentials
263+
uses: aws-actions/configure-aws-credentials@v4
264+
with:
265+
aws-access-key-id: ${{ secrets.aws_access_key_id }}
266+
aws-secret-access-key: ${{ secrets.aws_secret_access_key }}
267+
aws-region: eu-central-1
268+
- name: Get service githash
269+
id: git
270+
run: |
271+
hash=$(git rev-list -1 HEAD -- frontend)
272+
echo "githash=$hash" >> $GITHUB_OUTPUT
273+
- name: Check if commit is already on ECR
274+
id: image
275+
run: |
276+
set +e
277+
aws ecr describe-images --repository-name=pythonit/pycon-frontend --image-ids=imageTag=${{ steps.git.outputs.githash }}
278+
if [[ $? == 0 ]]; then
279+
echo "image_exists=1" >> $GITHUB_OUTPUT
280+
else
281+
echo "image_exists=0" >> $GITHUB_OUTPUT
282+
fi
283+
- name: Set up QEMU dependency
284+
if: ${{ steps.image.outputs.image_exists == 0 }}
285+
uses: docker/setup-qemu-action@v3
286+
- name: Login to GitHub Packages
287+
if: ${{ steps.image.outputs.image_exists == 0 }}
288+
uses: docker/login-action@v3
289+
with:
290+
registry: ghcr.io
291+
username: ${{ github.actor }}
292+
password: ${{ secrets.GITHUB_TOKEN }}
293+
- name: Login to Amazon ECR
294+
if: ${{ steps.image.outputs.image_exists == 0 }}
295+
uses: aws-actions/amazon-ecr-login@v2
296+
- name: Set up Docker Buildx
297+
id: buildx
298+
if: ${{ steps.image.outputs.image_exists == 0 }}
299+
uses: docker/setup-buildx-action@v3
300+
- name: Get vars
301+
id: vars
302+
if: ${{ steps.image.outputs.image_exists == 0 }}
256303
run: |
257-
curl -X POST ${{ secrets.VERCEL_DEPLOY_HOOK }}
304+
cms_hostname=$(aws ssm get-parameter --output text --query Parameter.Value --with-decryption --name /pythonit/${{ env.TF_WORKSPACE }}/pycon-frontend/cms-hostname)
305+
echo "CMS_HOSTNAME=$cms_hostname" >> "$GITHUB_OUTPUT"
306+
307+
conference_code=$(aws ssm get-parameter --output text --query Parameter.Value --with-decryption --name /pythonit/${{ env.TF_WORKSPACE }}/pycon-frontend/conference-code)
308+
echo "CONFERENCE_CODE=$conference_code" >> "$GITHUB_OUTPUT"
309+
- name: Build and push
310+
if: ${{ steps.image.outputs.image_exists == 0 }}
311+
uses: docker/build-push-action@v6
312+
with:
313+
context: ./frontend
314+
file: ./frontend/Dockerfile
315+
builder: ${{ steps.buildx.outputs.name }}
316+
provenance: false
317+
push: true
318+
tags: ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.eu-central-1.amazonaws.com/pythonit/pycon-frontend:${{ steps.git.outputs.githash }}
319+
cache-from: type=local,src=/tmp/.buildx-cache
320+
cache-to: type=local,dest=/tmp/.buildx-cache
321+
platforms: linux/arm64
322+
build-args: |
323+
API_URL_SERVER=https://${{ fromJSON('["pastaporto-", ""]')[github.ref == 'refs/heads/main'] }}admin.pycon.it
324+
CMS_ADMIN_HOST=${{ fromJSON('["pastaporto-", ""]')[github.ref == 'refs/heads/main'] }}admin.pycon.it
325+
CMS_HOSTNAME=${{ steps.vars.outputs.cms_hostname }}
326+
CONFERENCE_CODE=${{ steps.vars.outputs.conference_code }}
327+
328+
deploy-fe:
329+
runs-on: ubuntu-latest
330+
needs: [build-fe]
331+
environment:
332+
name: ${{ fromJSON('["pastaporto", "production"]')[github.ref == 'refs/heads/main'] }}
333+
defaults:
334+
run:
335+
working-directory: ./infrastructure/applications
336+
steps:
337+
- uses: actions/checkout@v4
338+
with:
339+
ref: ${{ github.ref }}
340+
fetch-depth: 0
341+
- uses: hashicorp/setup-terraform@v3
342+
with:
343+
terraform_version: 1.2.4
344+
- name: Terraform Init
345+
run: terraform init
346+
env:
347+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
348+
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
349+
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
350+
- name: Terraform Validate
351+
id: validate
352+
run: terraform validate -no-color
353+
354+
- name: Terraform apply
355+
run: terraform apply -no-color -auto-approve &> /dev/null
356+
env:
357+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
358+
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
359+
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
360+
AWS_DEFAULT_REGION: eu-central-1

frontend/Dockerfile

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
FROM node:23-alpine AS base
2+
3+
RUN apk add --no-cache curl
4+
5+
FROM base AS deps
6+
7+
RUN apk add --no-cache libc6-compat
8+
WORKDIR /app
9+
10+
COPY package.json pnpm-lock.yaml* .npmrc* ./
11+
RUN corepack enable pnpm && pnpm i --frozen-lockfile
12+
13+
FROM base AS builder
14+
15+
ARG API_URL_SERVER
16+
ARG CMS_HOSTNAME
17+
ARG CONFERENCE_CODE
18+
ARG CMS_ADMIN_HOST
19+
20+
WORKDIR /app
21+
22+
COPY --from=deps /app/node_modules ./node_modules
23+
COPY . .
24+
25+
RUN corepack enable pnpm && pnpm run build
26+
27+
FROM base AS runner
28+
WORKDIR /app
29+
30+
ENV NODE_ENV=production
31+
ENV NEXT_TELEMETRY_DISABLED=1
32+
33+
RUN addgroup --system --gid 1001 nodejs
34+
RUN adduser --system --uid 1001 nextjs
35+
36+
COPY --from=builder /app/public ./public
37+
38+
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
39+
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
40+
41+
USER nextjs
42+
43+
EXPOSE 3000
44+
45+
ENV PORT=3000
46+
47+
ENV HOSTNAME="0.0.0.0"
48+
CMD ["node", "server.js"]

frontend/next.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const {
1515
} = process.env;
1616

1717
module.exports = withSentryConfig({
18+
output: "standalone",
1819
i18n: {
1920
locales: ["default", "en", "it"],
2021
defaultLocale: "default",

frontend/src/pages/api/health.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default async function handler(req, res) {
2+
return res.json({ ok: true });
3+
}

infrastructure/applications/applications.tf

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,18 @@ module "pycon_backend" {
3434
}
3535
}
3636

37+
module "pycon_frontend" {
38+
source = "./pycon_frontend"
39+
cluster_id = module.cluster.cluster_id
40+
logs_group_name = module.cluster.logs_group_name
41+
server_ip = module.cluster.server_ip
42+
43+
providers = {
44+
aws = aws
45+
aws.us = aws.us
46+
}
47+
}
48+
3749
module "clamav" {
3850
source = "./clamav"
3951
cluster_id = module.cluster.cluster_id

infrastructure/applications/cluster/cloudfront.tf

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
locals {
2-
pycon_web_domain = local.is_prod ? "admin.pycon.it" : "${terraform.workspace}-admin.pycon.it"
2+
pycon_admin_domain = local.is_prod ? "admin.pycon.it" : "${terraform.workspace}-admin.pycon.it"
3+
pycon_frontend_domain = local.is_prod ? "frontend.pycon.it" : "${terraform.workspace}-frontend.pycon.it"
34
pretix_web_domain = local.is_prod ? "tickets.pycon.it" : "${terraform.workspace}-tickets.pycon.it"
45
}
56

@@ -22,9 +23,8 @@ resource "aws_cloudfront_distribution" "application" {
2223
is_ipv6_enabled = true
2324
comment = "${terraform.workspace} server"
2425
wait_for_deployment = false
25-
aliases = [
26-
local.pycon_web_domain,
27-
local.pretix_web_domain
26+
aliases = local.is_prod ? ["*.pycon.it", "pycon.it"] : [
27+
"*.pycon.it",
2828
]
2929

3030
origin {

infrastructure/applications/cluster/domains.tf

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,19 @@ data "aws_route53_zone" "zone" {
44

55
resource "aws_route53_record" "web_pycon" {
66
zone_id = data.aws_route53_zone.zone.zone_id
7-
name = local.pycon_web_domain
7+
name = local.pycon_admin_domain
8+
type = "A"
9+
10+
alias {
11+
name = aws_cloudfront_distribution.application.domain_name
12+
zone_id = aws_cloudfront_distribution.application.hosted_zone_id
13+
evaluate_target_health = false
14+
}
15+
}
16+
17+
resource "aws_route53_record" "web_frontend" {
18+
zone_id = data.aws_route53_zone.zone.zone_id
19+
name = local.pycon_frontend_domain
820
type = "A"
921

1022
alias {

infrastructure/applications/pycon_backend/worker.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ locals {
3030
},
3131
{
3232
name = "ALLOWED_HOSTS",
33-
value = ".pycon.it"
33+
value = ".pycon.it,${var.server_ip}"
3434
},
3535
{
3636
name = "DJANGO_SETTINGS_MODULE",
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
data "aws_ecr_repository" "repo" {
2+
name = "pythonit/pycon-frontend"
3+
}
4+
5+
data "aws_ecr_image" "image" {
6+
repository_name = data.aws_ecr_repository.repo.name
7+
image_tag = data.external.githash.result.githash
8+
}
9+
10+
data "aws_caller_identity" "current" {}
11+
12+
data "external" "githash" {
13+
program = ["python", abspath("${path.module}/../pretix/githash.py")]
14+
working_dir = abspath("${path.root}/../../frontend")
15+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module "secrets" {
2+
source = "../../components/secrets"
3+
service = "pycon-frontend"
4+
}
5+
6+
module "common_secrets" {
7+
source = "../../components/secrets"
8+
}

0 commit comments

Comments
 (0)