Skip to content

Deploy 2026.02.18

Deploy 2026.02.18 #132

Workflow file for this run

---
name: CI
on:
pull_request:
types: ["opened", "synchronize"]
push:
branches: ["prod", "develop"]
env:
COMPOSE_FILE_DEV: docker-compose.yaml
COMPOSE_FILE_PROD: prod-docker-compose.yaml
# See here for valid options:
# https://github.com/docker/setup-docker-action?tab=readme-ov-file#inputs.version
VERSION_DOCKER: latest
# See here for valid options:
# https://hub.docker.com/r/curlimages/curl/tags
VERSION_CURL: 7.80.0
permissions:
contents: read
actions: read
jobs:
lint-actions:
name: Lint:Actions
uses: freedomofpress/actionslib/.github/workflows/lint-actions.yaml@main
build-dev:
name: Build:Dev
uses: freedomofpress/actionslib/.github/workflows/oci-build.yaml@main
with:
context: '.'
containerfile: devops/docker/DevDjangoDockerfile
build-args: USERID=1001
requires-deep-history: true
registry: localhost/securedroporg-django
build-node:
name: Build:Node
uses: freedomofpress/actionslib/.github/workflows/oci-build.yaml@main
with:
context: '.'
containerfile: devops/docker/NodeDockerfile
build-args: USERID=1001
registry: localhost/securedroporg-node
build-prod:
name: Build:Prod
uses: freedomofpress/actionslib/.github/workflows/oci-build.yaml@main
with:
context: '.'
containerfile: devops/docker/ProdDjangoDockerfile
build-args: USERID=12345
registry: localhost/securedroporg
requires-deep-history: true
test:
name: Test:${{ matrix.target }}
runs-on: ubuntu-latest
needs:
- build-dev
- build-node
strategy:
matrix:
target:
- check-migrations
- bandit
- dev-tests
- flake8
steps:
- name: Install Docker
uses: docker/setup-docker-action@v4
with:
version: ${{ env.VERSION_DOCKER }}
- name: Checkout
uses: actions/checkout@v4
with:
persist-credentials: true
- name: Download dev image
uses: actions/download-artifact@v4
with:
name: ${{ needs.build-dev.outputs.artifact-name }}
path: ${{ runner.temp }}/images
- name: Download node image
uses: actions/download-artifact@v4
with:
name: ${{ needs.build-node.outputs.artifact-name }}
path: ${{ runner.temp }}/images
- name: Restore dev images
env:
LOAD_DEV_FILE: ${{ runner.temp }}/images/${{ needs.build-dev.outputs.artifact-image }}
IMAGE_DEV_URL: ${{ needs.build-dev.outputs.image-url }}
LOAD_NODE_FILE: ${{ runner.temp }}/images/${{ needs.build-node.outputs.artifact-image }}
IMAGE_NODE_URL: ${{ needs.build-node.outputs.image-url }}
run: |
docker image load --input "$LOAD_DEV_FILE"
docker image load --input "$LOAD_NODE_FILE"
docker image tag "$IMAGE_DEV_URL" localhost/securedroporg-django
docker image tag "$IMAGE_NODE_URL" localhost/securedroporg-node
- name: Create env config
run: make dev-init
- name: Start
run: |
docker compose --file="$COMPOSE_FILE_DEV" up --wait --pull=missing --no-build
# Check for failed services and error if any are found
sleep 5
if [[ $(docker compose --file="$COMPOSE_FILE_DEV" ps --status='[restarting, dead, exited]' --format=json) ]]; then
echo "ERROR: One or more services failed to start"
docker compose ps
exit 1
fi
- name: Make target ${{ matrix.target }}
run: make ${{ matrix.target }}
- name: Export logs
run: |
mkdir --parents ${{ runner.temp }}/logs
docker compose --file="$COMPOSE_FILE_DEV" logs django >${{ runner.temp }}/logs/django.log
docker compose --file="$COMPOSE_FILE_DEV" logs node >${{ runner.temp }}/logs/node.log
docker compose --file="$COMPOSE_FILE_DEV" logs postgresql >${{ runner.temp }}/logs/postgresql.log
- name: Save logs
uses: actions/upload-artifact@v4
with:
name: test-${{ matrix.target }}-${{ github.run_id }}-logs
path: ${{ runner.temp }}/logs
if-no-files-found: error
overwrite: true
retention-days: 5
- name: Save HTML coverage
uses: actions/upload-artifact@v4
if: ${{ matrix.target == 'dev-tests' }}
with:
name: test-${{ matrix.target }}-${{ github.run_id }}-htmlcov
path: htmlcov
if-no-files-found: error
overwrite: true
retention-days: 5
test-prod:
name: Test:Prod
runs-on: ubuntu-latest
needs:
- build-prod
steps:
- name: Install Docker
uses: docker/setup-docker-action@v4
with:
version: ${{ env.VERSION_DOCKER }}
- name: Checkout
uses: actions/checkout@v4
with:
persist-credentials: true
- name: Download prod image
uses: actions/download-artifact@v4
with:
name: ${{ needs.build-prod.outputs.artifact-name }}
path: ${{ runner.temp }}/images
- name: Restore prod image
env:
LOAD_FILE: ${{ runner.temp }}/images/${{ needs.build-prod.outputs.artifact-image }}
IMAGE_URL: ${{ needs.build-prod.outputs.image-url }}
run: |
docker image load --input "$LOAD_FILE"
docker image tag "$IMAGE_URL" quay.io/freedomofpress/securedroporg
- name: Start
run: |
docker compose --file="$COMPOSE_FILE_PROD" up --wait --pull=missing --no-build
# Check for failed services and error if any are found
sleep 5
if [[ $(docker compose --file="$COMPOSE_FILE_PROD" ps --status='[restarting, dead, exited]' --format=json) ]]; then
echo "ERROR: One or more services failed to start"
docker compose ps
exit 1
fi
- name: Verify django
run: |
docker run --pull=missing --rm --network=securedroporg_app "docker.io/curlimages/curl:$VERSION_CURL" \
--ipv4 --retry 24 --retry-delay 5 --retry-all-errors http://django:8000/health/ok/
- name: Verify database
run: |
docker compose --file="$COMPOSE_FILE_PROD" exec django \
/bin/bash -c "./manage.py createdevdata"
- name: Export logs
run: |
mkdir --parents ${{ runner.temp }}/logs
docker compose --file="$COMPOSE_FILE_PROD" logs django >${{ runner.temp }}/logs/django.log
docker compose --file="$COMPOSE_FILE_PROD" logs postgresql >${{ runner.temp }}/logs/postgresql.log
- name: Save logs
uses: actions/upload-artifact@v4
with:
name: build-prod-${{ github.run_id }}-logs
path: ${{ runner.temp }}/logs
if-no-files-found: error
overwrite: true
retention-days: 5
cleanup:
name: Cleanup
runs-on: ubuntu-latest
needs:
- build-dev
- build-node
- build-prod
- test
- test-prod
permissions:
contents: read
actions: write
if: ${{ always() }}
steps:
- name: Delete dev image
if: ${{ needs.build-dev.result == 'success' && always() }}
uses: freedomofpress/actionslib/act/delete-artifact@main
with:
artifact: ${{ needs.build-dev.outputs.artifact-name }}
error-if-absent: 'false'
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Delete node image
if: ${{ needs.build-node.result == 'success' && always() }}
uses: freedomofpress/actionslib/act/delete-artifact@main
with:
artifact: ${{ needs.build-node.outputs.artifact-name }}
error-if-absent: 'false'
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Delete prod image
if: ${{ needs.build-prod.result == 'success' && always() }}
uses: freedomofpress/actionslib/act/delete-artifact@main
with:
artifact: ${{ needs.build-prod.outputs.artifact-name }}
error-if-absent: 'false'
github-token: ${{ secrets.GITHUB_TOKEN }}