Skip to content

fix(deploy): resolve generated compose bind mounts against the projec… #1116

fix(deploy): resolve generated compose bind mounts against the projec…

fix(deploy): resolve generated compose bind mounts against the projec… #1116

Workflow file for this run

name: Docker CICD
on:
push:
branches:
- main
- testing
- dev
pull_request:
branches:
- main
- dev
release:
types: [published]
jobs:
cicd-docker:
name: Cargo and npm build
runs-on: ubuntu-latest
#runs-on: [self-hosted, linux]
# Build inside the same image the Dockerfile builds in, so the binaries
# produced here can be copied into the runtime image instead of being
# compiled a second time. They are dynamically linked against glibc, and
# glibc is forward- but not backward-compatible: a binary built on the
# runner (Ubuntu 24.04, glibc 2.39) would not start on debian:bookworm-slim
# (glibc 2.36). Building in rust:bookworm makes the two match exactly.
container:
image: rust:bookworm
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres"
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
SQLX_OFFLINE: true
steps:
- name: Checkout sources
uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
- name: Export PostgreSQL connection env
run: |
# Inside a job container, services resolve by name on the shared
# network — 127.0.0.1 is the container itself.
echo "PGHOST=postgres" >> "$GITHUB_ENV"
echo "PGPORT=5432" >> "$GITHUB_ENV"
echo "PGUSER=postgres" >> "$GITHUB_ENV"
echo "PGPASSWORD=postgres" >> "$GITHUB_ENV"
# No apt step here: `rust:bookworm` already carries pkg-config, libssl-dev
# and a C toolchain, and protoc never comes from the system — `build.rs`
# points PROTOC at a vendored binary unless one is already set. The step
# that used to be here called `sudo`, which the image does not have, and
# does not need: the job runs as root.
- name: Verify .sqlx cache exists
run: |
ls -lh .sqlx/ || echo ".sqlx directory not found"
find .sqlx -type f 2>/dev/null | wc -l
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
components: rustfmt, clippy
# Registry and git only — not `target`. Caching the target directory here
# filled the runner's disk: `restore-keys: docker-` pulled in an older,
# already-bloated cache, the build added to it, and the post-job step
# saved a larger one still, so every run grew the next one's starting
# point. A 33-minute job that passed every check then died in cleanup
# with "No space left on device".
#
# rust.yml reached the same conclusion for the same reason; this keeps
# both workflows on one rule.
- name: Cache Cargo registry/git (no target — it grows without bound here)
uses: Swatinem/rust-cache@v2
with:
cache-targets: "false"
key: docker-cicd
- name: Generate Secret Key
run: |
head -c16 /dev/urandom > src/secret.key
- name: Wait for PostgreSQL
run: |
for _ in $(seq 1 60); do
if bash -lc "exec 3<>/dev/tcp/${PGHOST}/${PGPORT}" 2>/dev/null; then
exit 0
fi
sleep 1
done
echo "PostgreSQL did not become ready in time" >&2
exit 1
- name: Cargo check
uses: actions-rs/cargo@v1
with:
command: check
- name: Install cargo-nextest
uses: taiki-e/install-action@nextest
# nextest runs each test in its own process, so tests that mutate global
# env vars no longer race and the suite runs in parallel (no more
# RUST_TEST_THREADS=1 serialization, no 25-minute timeout). The `bdd`
# target uses a custom harness nextest cannot run, so it runs separately.
# Both suites run even if one of them fails, so a single broken test does
# not hide the state of the other. Not `always()`: that also runs them
# after an earlier *setup* step fails, and then reports something
# unrelated — a missing apt package once surfaced as "no such command:
# nextest", because the install step had been skipped.
- name: Cargo test
run: cargo nextest run --tests -E 'not binary(bdd)'
- name: Cargo test (bdd suite)
if: success() || failure()
run: cargo test --test bdd
- name: Rustfmt
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
components: rustfmt
command: fmt
args: --all -- --check
- name: Rustfmt
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
components: clippy
command: clippy
args: -- -D warnings
# One invocation, so the four binaries share a single compilation of the
# workspace instead of four sequential ones. These are the binaries the
# runtime image needs; the Docker job copies them rather than rebuilding.
# Two invocations, not one: `--features` applies to the whole command, not
# to the `--bin` it follows. Listing them together builds `server` with
# `explain` too — a differently configured binary from the one the image
# has always shipped. The second call is nearly free; only `console` and
# the re-featured casbin dependency recompile.
- name: Build release binaries
run: |
cargo build --release \
--bin server \
--bin cleanup-notify \
--bin backfill_field_policy
cargo build --release --bin console --features explain
- name: Set up Node.js
if: ${{ hashFiles('web/package.json') != '' }}
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: web/package-lock.json
- name: npm install, build, and test
if: ${{ hashFiles('web/package.json') != '' }}
working-directory: ./web
run: |
npm install
npm run build
# npm test
- name: Archive production artifacts
if: ${{ hashFiles('web/package.json') != '' }}
uses: actions/upload-artifact@v4
with:
name: dist-without-markdown
path: |
web/dist
!web/dist/**/*.md
- name: Display structure of downloaded files
if: ${{ hashFiles('web/package.json') != '' }}
run: ls -R web/dist
- name: Copy app files and zip
run: |
mkdir -p app/stacker/dist
cp target/release/server app/stacker/server
cp target/release/console app/stacker/console
cp target/release/cleanup-notify app/stacker/cleanup-notify
cp target/release/backfill_field_policy app/stacker/backfill_field_policy
if [ -d web/dist ]; then cp -a web/dist/. app/stacker; fi
cp Dockerfile app/Dockerfile
cp access_control.conf.dist app/access_control.conf.dist
cd app
touch .env
tar -czvf ../app.tar.gz .
cd ..
- name: Upload app archive for Docker job
uses: actions/upload-artifact@v4
with:
name: artifact-linux-docker
path: app.tar.gz
cicd-linux-docker:
name: CICD Docker
runs-on: ubuntu-latest
#runs-on: [self-hosted, linux]
needs: cicd-docker
steps:
- name: Checkout sources
uses: actions/checkout@v4
with:
ref: ${{ github.event_name == 'pull_request' && github.head_ref || github.ref }}
- name: Verify shared pipe fixtures
run: |
test -d "${GITHUB_WORKSPACE}/tests/fixtures/pipe-contract"
# The test job already compiled these, in the same rust:bookworm image the
# runtime stage is based on. Without this the Dockerfile compiles the whole
# workspace a second time — around fourteen minutes of the run.
- name: Download binaries built by the test job
uses: actions/download-artifact@v4
with:
name: artifact-linux-docker
# Unpacked outside the workspace: `.dockerignore` is empty, so anything
# left here is sent to buildkit as part of `context: .` — hundreds of
# megabytes of release binaries, twice, eating back the time this change
# exists to save.
- name: Unpack binaries
run: |
mkdir -p "${{ runner.temp }}/prebuilt"
tar -xzf app.tar.gz -C "${{ runner.temp }}/prebuilt"
cd "${{ runner.temp }}/prebuilt"
mv stacker/server stacker/console stacker/cleanup-notify \
stacker/backfill_field_policy .
chmod +x server console cleanup-notify backfill_field_policy
rm -f "${GITHUB_WORKSPACE}/app.tar.gz"
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Set Docker tags
id: docker_tags
run: |
if [ "${{ github.event_name }}" = "release" ]; then
echo "tags=trydirect/stacker:${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
else
echo "tags=trydirect/stacker:latest" >> "$GITHUB_OUTPUT"
fi
-
name: Build and push
uses: docker/build-push-action@v6
with:
context: .
build-args: |
BINARIES=prebuilt
build-contexts: |
shared_fixtures=${{ github.workspace }}/tests/fixtures
prebuilt_binaries=${{ runner.temp }}/prebuilt
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_tags.outputs.tags }}
stackerdb-docker:
name: StackerDB Docker
runs-on: ubuntu-latest
#runs-on: [self-hosted, linux]
steps:
- name: Checkout sources
uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Set Docker tags
id: stackerdb_tags
run: |
echo "tags<<EOF" >> "$GITHUB_OUTPUT"
echo "trydirect/stackerdb:build-${{ github.run_number }}" >> "$GITHUB_OUTPUT"
echo "trydirect/stackerdb:latest" >> "$GITHUB_OUTPUT"
if [ "${{ github.event_name }}" = "release" ]; then
echo "trydirect/stackerdb:${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
fi
echo "EOF" >> "$GITHUB_OUTPUT"
- name: Build and push
uses: docker/build-push-action@v6
with:
context: ./stackerdb
file: ./stackerdb/Dockerfile
push: true
tags: ${{ steps.stackerdb_tags.outputs.tags }}
cache-from: type=gha,scope=stackerdb
cache-to: type=gha,mode=max,scope=stackerdb