Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
27d0ff6
use .env.prod in docker-compose.prod.yaml
paigewilliams Feb 3, 2026
5e5cc5b
add github action to publish docker image to github packages
paigewilliams Feb 3, 2026
e91eb06
point push step to prod dockerfile
paigewilliams Feb 3, 2026
ec83f25
try triggering action on push
paigewilliams Feb 3, 2026
453f95e
try generating slug for repo name
paigewilliams Feb 3, 2026
4d93d8d
try getting slug for repo name as string
paigewilliams Feb 3, 2026
43916a5
try again to use lowercase repo name
paigewilliams Feb 3, 2026
521e750
try setting lowercase IMAGE_NAME env var
paigewilliams Feb 3, 2026
05fcd1b
add prod dockerfile
paigewilliams Feb 3, 2026
76b4ab0
remove cache lines for build and push step
paigewilliams Feb 4, 2026
5fb47f5
try token change
paigewilliams Feb 4, 2026
942ea13
remove push from trigger options
paigewilliams Feb 4, 2026
c83e88c
use common docker compose and use extends
paigewilliams Feb 4, 2026
3e60eaa
wip: add gh action to push to ECR and deploy to app runner
paigewilliams Feb 4, 2026
cddcf48
set 5432 as default db port
paigewilliams Feb 24, 2026
bd97aac
use nginx proxy in prod setup
paigewilliams Feb 24, 2026
fd1ec89
add docker compose for testing prod set up locally
paigewilliams Feb 24, 2026
7f91476
add OpenTofu/terraform to gitignore
paigewilliams Feb 24, 2026
60d0dcc
add tf/opentofu IaC
paigewilliams Feb 26, 2026
e8e2c00
track terraform.lock.hcl
paigewilliams Feb 26, 2026
1d46d36
remove deploy app runner gh action workflow
paigewilliams Feb 26, 2026
476f04b
use commands in docker compose not dockerfiles; remove prod.Dockerfile
paigewilliams Feb 26, 2026
1d13c2d
add user_data script to use on install
paigewilliams Feb 27, 2026
aa5786e
remove TODO
paigewilliams Feb 27, 2026
e5f6998
clean up infra files
paigewilliams Mar 2, 2026
d55fbfe
add step in gh action to build and push proxy docker image
paigewilliams Mar 2, 2026
d26b090
add action run on push for testing
paigewilliams Mar 2, 2026
dda7f30
remove format repo slug step in gh action
paigewilliams Mar 2, 2026
cedfa41
spin up docker containers in user_data script
paigewilliams Mar 3, 2026
52c174b
fix syntax in attestation step
paigewilliams Mar 3, 2026
ef93a8e
fix attestation step for images
paigewilliams Mar 3, 2026
7cca80b
wip: gh action to build and push to ECR
paigewilliams Mar 3, 2026
763cc13
update cadence for gh actions
paigewilliams Mar 3, 2026
728a1b8
wip: debug gh action
paigewilliams Mar 3, 2026
4b16ff5
point to correct dockerfile locations
paigewilliams Mar 3, 2026
8f3efe4
use multiarchitechure builds in gh action
paigewilliams Mar 3, 2026
3a8e9ab
wip: use ssm to deploy docker
paigewilliams Mar 3, 2026
87c5b21
only manually trigger create-and-publish-docker-images gh action
paigewilliams Mar 3, 2026
130d0fb
wip: comment out build step, fix syntax in ssm commands
paigewilliams Mar 3, 2026
3513e94
try verifying deployment status
paigewilliams Mar 3, 2026
0f25d03
remove -p from docker compose up
paigewilliams Mar 3, 2026
2c28abd
comment back in docker build image steps
paigewilliams Mar 3, 2026
49ddfa5
trigger ec2 on push to develop branch
paigewilliams Mar 5, 2026
ac50543
push to GHR on merge to main
paigewilliams Mar 5, 2026
4df94c8
push to GHR on merge to main
paigewilliams Mar 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions .github/workflows/create-and-publish-docker-images.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# source: https://docs.github.com/en/actions/tutorials/publish-packages/publish-docker-images#publishing-images-to-docker-hub-and-github-packages
name: Create and publish a Docker image

# manually trigger while testing
on:
push:
branches:
- main
workflow_dispatch:

# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds.
env:
REGISTRY: ghcr.io

jobs:
build-and-push-image:
runs-on: ubuntu-latest
# Sets the permissions granted to the `PACKAGE_TOKEN` for the actions in this job.
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v5
# Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.PACKAGE_TOKEN }}
# This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels.
- name: Set lowercase image name
run: |
IMAGE_NAME=$(echo "${GITHUB_REPOSITORY}" | tr '[:upper:]' '[:lower:]')
echo "IMAGE_NAME=${IMAGE_NAME}" >> $GITHUB_ENV
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages.
# It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see [Usage](https://github.com/docker/build-push-action#usage) in the README of the `docker/build-push-action` repository.
# It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.

- name: Build and push web Docker image
id: push
uses: docker/build-push-action@v6
with:
context: ./TEKDB
file: ./TEKDB/Dockerfile
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/web:latest,${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/web:${{ github.sha }}
labels: ${{ steps.meta.outputs.labels }}
- name: Build and push proxy Docker image
id: push-proxy
uses: docker/build-push-action@v6
with:
context: ./proxy
file: ./proxy/Dockerfile
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/proxy:latest,${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/proxy:${{ github.sha }}
labels: ${{ steps.meta.outputs.labels }}

Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This workflow only builds and pushes the web image to GHCR. However, docker/docker-compose.prod.yaml references ${ITKDB_PROXY_ECR_PATH}:latest for the proxy service, and infra/ecr.tf creates an aws_ecr_repository for the proxy. There is no step in this workflow (or any other visible workflow) that builds and pushes the proxy image. Without a published proxy image, the production deployment will fail when it attempts to pull it. A separate build step for the proxy image (context: ./proxy, file: ./proxy/Dockerfile) should be added.

Suggested change
- name: Build and push proxy Docker image
id: push_proxy
uses: docker/build-push-action@v6
with:
context: ./proxy
file: ./proxy/Dockerfile
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/proxy:latest,${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/proxy:${{ github.sha }}
labels: ${{ steps.meta.outputs.labels }}

Copilot uses AI. Check for mistakes.
# This step generates an artifact attestation for the image, which is an unforgeable statement about where and how it was built. It increases supply chain security for people who consume the image. For more information, see [Using artifact attestations to establish provenance for builds](/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds).
- name: Generate artifact attestation for web image
uses: actions/attest-build-provenance@v3
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/web
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
- name: Generate artifact attestation for proxy image
uses: actions/attest-build-provenance@v3
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/proxy
subject-digest: ${{ steps.push-proxy.outputs.digest }}
push-to-registry: true

107 changes: 107 additions & 0 deletions .github/workflows/deploy-ec2-staging.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
name: Build, Push, and Deploy to EC2 Staging

on:
push:
branches:
- develop
Comment on lines +3 to +6
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to deploy to ec2 staging on push to the develop branch. Need to add to the ec2 user data script to switch to the develop branch. But this needs to be merged first. kind of a 🐔 and 🥚 situation

workflow_dispatch:

jobs:
build-push-and-deploy:
name: Build, Push to ECR, Deploy to EC2 Staging
runs-on: ubuntu-latest
permissions:
id-token: write

steps:
- name: Checkout code
uses: actions/checkout@v5

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ${{ secrets.AWS_REGION}}

- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build, tag, and push web Docker image to ECR
id: build-web-image
env:
IMAGE_TAG: ${{ github.sha }}
IMAGE_URI: ${{ secrets.WEB_ECR_IMAGE_URI }}
run: |
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t $IMAGE_URI:$IMAGE_TAG \
-t $IMAGE_URI:latest \
-f ./TEKDB/Dockerfile ./TEKDB \
--push

- name: Build, tag, and push proxy Docker image to ECR
id: build-proxy-image
env:
IMAGE_TAG: ${{ github.sha }}
IMAGE_URI: ${{ secrets.PROXY_ECR_IMAGE_URI }}
run: |
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t $IMAGE_URI:$IMAGE_TAG \
-t $IMAGE_URI:latest \
-f ./proxy/Dockerfile ./proxy \
--push
- name: get EC2 Instance ID
id: get-instance
run: |
INSTANCE_ID=$(aws ec2 describe-instances \
--filters "Name=tag:Name,Values=${{ secrets.EC2_INSTANCE_NAME }}" \
"Name=instance-state-name,Values=running" \
--query "Reservations[0].Instances[0].InstanceId" \
--output text)
echo "instance_id=$INSTANCE_ID" >> $GITHUB_OUTPUT
- name: Deploy via SSM
id: ssm-deploy
run: |
COMMAND_ID=$(aws ssm send-command \
--instance-ids "${{ steps.get-instance.outputs.instance_id }}" \
--document-name "AWS-RunShellScript" \
--parameters 'commands=[
"aws ecr get-login-password --region ${{ secrets.AWS_REGION }} | sudo docker login --username AWS --password-stdin ${{ secrets.WEB_ECR_IMAGE_URI }}",
"sudo docker pull ${{ secrets.WEB_ECR_IMAGE_URI }}:latest",
"aws ecr get-login-password --region ${{ secrets.AWS_REGION }} | sudo docker login --username AWS --password-stdin ${{ secrets.PROXY_ECR_IMAGE_URI }}",
"sudo docker pull ${{ secrets.PROXY_ECR_IMAGE_URI }}:latest",
"cd /TEKDB",
"sudo docker compose down",
"docker compose --env-file docker/.env.prod -f docker/docker-compose.prod.yaml up -d",
"sleep 5",
"docker ps --filter name=web --format \"{{.Status}}\""
]' \
--timeout-seconds 120 \
--comment "Deploy ${{ github.sha }}" \
--query "Command.CommandId" \
--output text)
echo "command_id=$COMMAND_ID" >> $GITHUB_OUTPUT
- name: Wait for deployment to complete
run: |
aws ssm wait command-executed \
--command-id ${{ steps.ssm-deploy.outputs.command_id }} \
--instance-id ${{ steps.get-instance.outputs.instance_id }}
- name: Verify deployment result
run: |
RESULT=$(aws ssm get-command-invocation \
--command-id ${{ steps.ssm-deploy.outputs.command_id }} \
--instance-id ${{ steps.get-instance.outputs.instance_id }} \
--query "{Status:Status,Output:StandardOutputContent,Error:StandardErrorContent}" \
--output json)
echo "$RESULT" | jq .
STATUS=$(echo "$RESULT" | jq -r '.Status')
if [ "$STATUS" != "Success" ]; then
echo "::error::Deployment failed with status: $STATUS"
exit 1
fi
echo "Deployment succeeded."
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ celerybeat-schedule

# dotenv
.env
.env.prod

# virtualenv
venv/
Expand All @@ -98,4 +99,10 @@ ENV/
.ropeproject

#vscode settings
.vscode/
.vscode/

# OpenTofu/Terraform
.terraform/
terraform.tfstate
terraform.tfstate.backup
*.tfvars
2 changes: 0 additions & 2 deletions TEKDB/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,3 @@ ENV DJANGO_SETTINGS_MODULE=TEKDB.settings

ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]

Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TEKDB/Dockerfile removes the default CMD ["dev"] with no replacement. With no CMD, and ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] set, running the container with no command argument means entrypoint.sh receives no arguments. The entrypoint.sh else branch does exec "$@", which with no arguments will try to execute an empty string and fail. A default CMD should be provided (e.g. CMD ["dev"]) or the entrypoint script should handle the no-argument case gracefully.

Suggested change
CMD ["dev"]

Copilot uses AI. Check for mistakes.
# use development server by default
CMD ["dev"]
2 changes: 1 addition & 1 deletion TEKDB/TEKDB/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@
"USER": os.environ.get("SQL_USER", "postgres"),
"PASSWORD": os.environ.get("SQL_PASSWORD", None),
"HOST": os.environ.get("SQL_HOST", "db"),
"PORT": os.environ.get("SQL_PORT", None),
"PORT": os.environ.get("SQL_PORT", 5432),
}
}

Expand Down
7 changes: 5 additions & 2 deletions TEKDB/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,11 @@ if [ "$(python manage.py shell -c 'from TEKDB.models import LookupPlanningUnit;
fi

if [ "$1" = "prod" ]; then
echo "Starting uWSGI (HTTP) on :8000"
uwsgi --http :8000 --master --enable-threads --module TEKDB.wsgi
echo "Starting uWSGI (socket) on :8000"
uwsgi --socket :8000 --master --enable-threads --module TEKDB.wsgi
elif [ "$1" = "prod-local" ]; then
echo "Starting uWSGI (http) on :8000 with local settings"
uwsgi --http :8000 --master --enable-threads --module TEKDB.wsgi
elif [ "$1" = "dev" ]; then
echo "Starting python development server on :8000"
python manage.py runserver 0.0.0.0:8000
Expand Down
19 changes: 2 additions & 17 deletions docker/docker-compose.yml → docker/common.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ services:
interval: 10s
timeout: 5s
retries: 5

web:
build:
context: ../TEKDB/
Expand All @@ -26,20 +26,5 @@ services:
- db
env_file:
- .env.dev
environment:
ALLOWED_HOSTS: ${ALLOWED_HOSTS}
DEBUG: ${DEBUG}
SQL_ENGINE: ${SQL_ENGINE}
SQL_HOST: ${SQL_HOST}
SQL_PORT: ${SQL_PORT}
SQL_DATABASE: ${SQL_DATABASE}
SQL_USER: ${SQL_USER}
SQL_PASSWORD: ${SQL_PASSWORD}
SECRET_KEY: ${SECRET_KEY}
ports:
- "8000:8000"
volumes:
- ../TEKDB:/usr/src/app

volumes:
tekdb_db_data:
- "8000:8000"
34 changes: 34 additions & 0 deletions docker/docker-compose.prod.local.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
services:
db:
extends:
file: common.yaml
service: db

web:
extends:
file: common.yaml
service: web
command: ["prod-local"]
env_file:
- .env.dev
ports: []
volumes:
- static_volume:/usr/src/app/static
- media_volume:/usr/src/app/media
proxy:
build:
context: ../proxy/
dockerfile: ../proxy/Dockerfile
restart: unless-stopped
depends_on:
- web
ports:
- "8080:8080"
volumes:
- static_volume:/vol/static/static:ro
- media_volume:/vol/static/media:ro

volumes:
tekdb_db_data:
static_volume:
media_volume:
55 changes: 21 additions & 34 deletions docker/docker-compose.prod.yaml
Original file line number Diff line number Diff line change
@@ -1,46 +1,33 @@
services:
db:
image: postgis/postgis:15-3.4
restart: always
platform: linux/amd64
environment:
POSTGRES_DB: ${SQL_DATABASE}
POSTGRES_USER: ${SQL_USER}
POSTGRES_PASSWORD: ${SQL_PASSWORD}
volumes:
- tekdb_db_data:/var/lib/postgresql/data
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${SQL_USER} -d ${SQL_DATABASE} -h localhost -p ${SQL_PORT}"]
interval: 10s
timeout: 5s
retries: 5
extends:
file: common.yaml
service: db

web:
build:
context: ../TEKDB/
dockerfile: ../TEKDB/Dockerfile
extends:
file: common.yaml
service: web
image: ${ITKDB_ECR_PATH}:latest
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workflow pushes images to GHCR (GitHub Container Registry at ghcr.io), but the production Docker Compose file uses ${ITKDB_ECR_PATH} and ${ITKDB_PROXY_ECR_PATH}, which are Amazon ECR paths. The infra code also provisions ECR repositories (infra/ecr.tf). There is a mismatch: this workflow doesn't publish to ECR at all, and there's no workflow in place to authenticate with AWS ECR and push there. Either the docker-compose.prod.yaml should point to GHCR images, or this workflow needs to be updated to authenticate and push to ECR.

Copilot uses AI. Check for mistakes.
command: ["prod"]
env_file:
- .env.prod
Comment on lines +13 to +14
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The web service in docker-compose.prod.yaml adds env_file: [.env.prod] via extends from common.yaml which already has env_file: [.env.dev]. Docker Compose merges list-type keys when using extends, so both .env.dev and .env.prod will be loaded. Values from .env.prod will take precedence over .env.dev (last file wins) when both define the same key, but the development secrets from .env.dev will still be passed into the container as well. If .env.dev is not present on the production server this may cause a startup failure. Consider explicitly overriding env_file to only include .env.prod rather than relying on merge behavior.

Copilot uses AI. Check for mistakes.
ports: []
volumes:
- static_volume:/usr/src/app/static
- media_volume:/usr/src/app/media
proxy:
image: ${ITKDB_PROXY_ECR_PATH}:latest
restart: unless-stopped
depends_on:
- db
env_file:
- .env.dev
environment:
ALLOWED_HOSTS: ${ALLOWED_HOSTS}
DEBUG: ${DEBUG}
SQL_ENGINE: ${SQL_ENGINE}
SQL_HOST: ${SQL_HOST}
SQL_PORT: ${SQL_PORT}
SQL_DATABASE: ${SQL_DATABASE}
SQL_USER: ${SQL_USER}
SQL_PASSWORD: ${SQL_PASSWORD}
SECRET_KEY: ${SECRET_KEY}
- web
ports:
- "8000:8000"
- "80:8080"
volumes:
- ../TEKDB:/usr/src/app
- static_volume:/vol/static/static:ro
- media_volume:/vol/static/media:ro

volumes:
tekdb_db_data:
static_volume:
media_volume:
Loading