Skip to content

Commit 7a4f42f

Browse files
AlexF4Devclaude
andcommitted
migrate image tags from docker-compose.yaml to .env
Move BACKEND_TAG/FRONTEND_TAG into a co-located .env file that Docker Compose loads automatically. CI now uses source/sed instead of yq for tag operations, eliminating the yq dependency from all workflows except lint-yaml. Rename the reusable workflow input from composePath to envPath. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 089a3ae commit 7a4f42f

21 files changed

Lines changed: 143 additions & 190 deletions

.claude/CLAUDE.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@ Ready-to-use workflow files are in [`templates/gitops-ci/`](./templates/gitops-c
1717

1818
## Demo
1919

20-
The `demo/` directory is a filled-in copy of the templates using `ghcr.io/aquasecurity/trivy` (public image with many semver tags). Backend pinned to `0.24.0`, frontend to `0.23.0` — deliberately old so tag discovery always finds a diff. See [demo/README.md](./demo/README.md).
20+
The `demo/` directory is a filled-in copy of the templates using `ghcr.io/aquasecurity/trivy` (public image with many semver tags). Backend pinned to `0.24.0`, frontend to `0.23.0` in `.env` — deliberately old so tag discovery always finds a diff. See [demo/README.md](./demo/README.md).
2121

2222
The `.github/` directory contains the same demo workflows configured to actually run on this repo (cron disabled, deployment is echo-only).
2323

2424
## CI & Testing
2525

2626
- **CI dry-run** (`.github/workflows/ci-dryrun.yaml`) — 5 parallel jobs: lint-yaml, lint-shell, version extraction, GHCR tag discovery, release PR dry-run. Runs on push to main and PRs.
27-
- **Local smoke test** (`test/smoke.sh`) — validates YAML, version extraction, semver regex, shellcheck, and placeholder replacement. Requires `yq` and `shellcheck` (gracefully skips if missing).
27+
- **Local smoke test** (`test/smoke.sh`) — validates YAML, `.env` file, version extraction, semver regex, shellcheck, and placeholder replacement. Requires `shellcheck` (gracefully skips if missing); `yq` needed only for YAML validation.
2828
- **Manual triggers** — all workflows support `workflow_dispatch` for on-demand testing.
2929

3030
## Key Patterns
@@ -35,16 +35,17 @@ The `.github/` directory contains the same demo workflows configured to actually
3535
4. **GHCR tag discovery** — Custom composite action that paginates the GHCR API, exchanges tokens for cross-org access, and filters by semver.
3636
5. **Auto-merge with retry**`peter-evans/create-pull-request` + `nick-fields/retry` to handle branch protection race conditions.
3737
6. **Scheduled run cleanup** — Deletes old successful cron runs to keep the Actions tab clean.
38-
7. **Slack notifications** — Reports deployed versions extracted from `docker-compose.yaml`.
38+
7. **Slack notifications** — Reports deployed versions extracted from `.env`.
3939

4040
## Gotchas
4141

4242
- `env` context is **not available** in `with:` for reusable workflow calls — values must be inlined.
4343
- `GITHUB_TOKEN` cannot read packages from other orgs — the ghcr-latest-tag action exchanges it for a GHCR-scoped token.
4444
- The default tag pattern is strict semver (`X.Y.Z`) to exclude arch-specific tags like `0.25.2-s390x`.
45+
- Image tags live in `.env` (not `docker-compose.yaml`). Docker Compose auto-loads `.env` from the same directory.
4546

4647
## Applying to Monorepos
4748

4849
- Use `paths` filters to only build/deploy what changed
4950
- Combine with `turbo run build --filter=...[origin/main]` for Turborepo change detection
50-
- Call the reusable workflow multiple times with different `baseBranch`/`composePath` for staging vs production
51+
- Call the reusable workflow multiple times with different `baseBranch`/`envPath` for staging vs production

.github/workflows/ci-create-release-pr.yaml

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# GitOps Release PR Creator (Reusable Workflow) — Demo
33
#
44
# Called by ci-pr-trigger.yaml. Compares new image tags against current
5-
# docker-compose.yaml and creates/updates a PR if versions differ.
5+
# .env file and creates/updates a PR if versions differ.
66
###############################################################################
77

88
name: "CI: Create release PR"
@@ -22,8 +22,8 @@ on:
2222
description: "New frontend image tag"
2323
required: false
2424
type: string
25-
composePath:
26-
description: "Path to docker-compose.yaml"
25+
envPath:
26+
description: "Path to .env file with image tags"
2727
required: true
2828
type: string
2929

@@ -35,22 +35,15 @@ jobs:
3535
with:
3636
ref: ${{ inputs.baseBranch }}
3737

38-
- name: Install yq
39-
uses: mikefarah/yq@v4.44.6
40-
4138
- name: Read current image tags
4239
id: current
4340
run: |
44-
backendImage=$(yq e '.services.backend.image' ${{ inputs.composePath }} | tr -d '"')
45-
frontendImage=$(yq e '.services.frontend.image' ${{ inputs.composePath }} | tr -d '"')
46-
47-
currentBackendTag=${backendImage##*:}
48-
currentFrontendTag=${frontendImage##*:}
41+
source ${{ inputs.envPath }}
4942
50-
echo "currentBackendTag=${currentBackendTag}" >> $GITHUB_OUTPUT
51-
echo "currentFrontendTag=${currentFrontendTag}" >> $GITHUB_OUTPUT
52-
echo "Current backend: ${currentBackendTag}"
53-
echo "Current frontend: ${currentFrontendTag}"
43+
echo "currentBackendTag=${BACKEND_TAG}" >> $GITHUB_OUTPUT
44+
echo "currentFrontendTag=${FRONTEND_TAG}" >> $GITHUB_OUTPUT
45+
echo "Current backend: ${BACKEND_TAG}"
46+
echo "Current frontend: ${FRONTEND_TAG}"
5447
5548
- name: Update image tags if changed
5649
id: update
@@ -59,19 +52,19 @@ jobs:
5952
FRONTEND_TAG: ${{ inputs.frontendTag }}
6053
CURRENT_BACKEND_TAG: ${{ steps.current.outputs.currentBackendTag }}
6154
CURRENT_FRONTEND_TAG: ${{ steps.current.outputs.currentFrontendTag }}
62-
COMPOSE_PATH: ${{ inputs.composePath }}
55+
ENV_PATH: ${{ inputs.envPath }}
6356
run: |
6457
changed=false
6558
6659
if [[ -n "${BACKEND_TAG}" && "${BACKEND_TAG}" != "${CURRENT_BACKEND_TAG}" ]]; then
6760
echo "Updating backend: ${CURRENT_BACKEND_TAG} -> ${BACKEND_TAG}"
68-
yq -i ".services.backend.image = \"ghcr.io/aquasecurity/trivy:${BACKEND_TAG}\"" "${COMPOSE_PATH}"
61+
sed -i "s/^BACKEND_TAG=.*/BACKEND_TAG=${BACKEND_TAG}/" "${ENV_PATH}"
6962
changed=true
7063
fi
7164
7265
if [[ -n "${FRONTEND_TAG}" && "${FRONTEND_TAG}" != "${CURRENT_FRONTEND_TAG}" ]]; then
7366
echo "Updating frontend: ${CURRENT_FRONTEND_TAG} -> ${FRONTEND_TAG}"
74-
yq -i ".services.frontend.image = \"ghcr.io/aquasecurity/trivy:${FRONTEND_TAG}\"" "${COMPOSE_PATH}"
67+
sed -i "s/^FRONTEND_TAG=.*/FRONTEND_TAG=${FRONTEND_TAG}/" "${ENV_PATH}"
7568
changed=true
7669
fi
7770

.github/workflows/ci-deployment-self-hosted.yaml

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,6 @@ jobs:
3333
steps:
3434
- uses: actions/checkout@v4
3535

36-
- name: Install yq
37-
run: |
38-
sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/v4.44.6/yq_linux_amd64
39-
sudo chmod +x /usr/local/bin/yq
40-
4136
- name: "[dry-run] Copy files to deployment directory"
4237
run: |
4338
echo "Would copy ${{ env.PROJECT_PATH }}/* -> ${{ env.REMOTE_PATH }}/"
@@ -54,12 +49,11 @@ jobs:
5449
- name: Get deployed versions
5550
id: versions
5651
run: |
57-
BACKEND=$(yq e '.services.backend.image' ${{ env.PROJECT_PATH }}/docker-compose.yaml | rev | cut -d: -f1 | rev)
58-
FRONTEND=$(yq e '.services.frontend.image' ${{ env.PROJECT_PATH }}/docker-compose.yaml | rev | cut -d: -f1 | rev)
59-
echo "backend=${BACKEND}" >> $GITHUB_OUTPUT
60-
echo "frontend=${FRONTEND}" >> $GITHUB_OUTPUT
61-
echo "Backend: ${BACKEND}"
62-
echo "Frontend: ${FRONTEND}"
52+
source ${{ env.PROJECT_PATH }}/.env
53+
echo "backend=${BACKEND_TAG}" >> $GITHUB_OUTPUT
54+
echo "frontend=${FRONTEND_TAG}" >> $GITHUB_OUTPUT
55+
echo "Backend: ${BACKEND_TAG}"
56+
echo "Frontend: ${FRONTEND_TAG}"
6357
6458
notify:
6559
needs: deploy

.github/workflows/ci-dryrun.yaml

Lines changed: 21 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -56,41 +56,23 @@ jobs:
5656
steps:
5757
- uses: actions/checkout@v4
5858

59-
- name: Install yq
60-
run: |
61-
sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/v4.44.6/yq_linux_amd64
62-
sudo chmod +x /usr/local/bin/yq
63-
64-
- name: Extract and assert versions from demo compose
59+
- name: Extract and assert versions from demo .env
6560
run: |
66-
COMPOSE="demo/projects/demo-app/docker-compose.yaml"
67-
68-
# Method 1: rev | cut (used in deployment workflow)
69-
BACKEND_REV=$(yq e '.services.backend.image' "$COMPOSE" | rev | cut -d: -f1 | rev)
70-
FRONTEND_REV=$(yq e '.services.frontend.image' "$COMPOSE" | rev | cut -d: -f1 | rev)
61+
ENV_FILE="demo/projects/demo-app/.env"
7162
72-
# Method 2: ${##*:} (used in release PR workflow)
73-
backendImage=$(yq e '.services.backend.image' "$COMPOSE" | tr -d '"')
74-
frontendImage=$(yq e '.services.frontend.image' "$COMPOSE" | tr -d '"')
75-
BACKEND_PE=${backendImage##*:}
76-
FRONTEND_PE=${frontendImage##*:}
63+
source "${ENV_FILE}"
7764
78-
echo "rev|cut — backend=${BACKEND_REV} frontend=${FRONTEND_REV}"
79-
echo '##*: — backend='"${BACKEND_PE}"' frontend='"${FRONTEND_PE}"
65+
echo "backend=${BACKEND_TAG} frontend=${FRONTEND_TAG}"
8066
8167
errors=0
82-
for method in REV PE; do
83-
bvar="BACKEND_${method}"
84-
fvar="FRONTEND_${method}"
85-
if [[ "${!bvar}" != "0.24.0" ]]; then
86-
echo "FAIL: ${bvar}=${!bvar}, expected 0.24.0"
87-
errors=$((errors + 1))
88-
fi
89-
if [[ "${!fvar}" != "0.23.0" ]]; then
90-
echo "FAIL: ${fvar}=${!fvar}, expected 0.23.0"
91-
errors=$((errors + 1))
92-
fi
93-
done
68+
if [[ "${BACKEND_TAG}" != "0.24.0" ]]; then
69+
echo "FAIL: BACKEND_TAG=${BACKEND_TAG}, expected 0.24.0"
70+
errors=$((errors + 1))
71+
fi
72+
if [[ "${FRONTEND_TAG}" != "0.23.0" ]]; then
73+
echo "FAIL: FRONTEND_TAG=${FRONTEND_TAG}, expected 0.23.0"
74+
errors=$((errors + 1))
75+
fi
9476
9577
if [[ $errors -gt 0 ]]; then
9678
echo "::error::${errors} version extraction assertion(s) failed"
@@ -132,26 +114,21 @@ jobs:
132114
steps:
133115
- uses: actions/checkout@v4
134116

135-
- name: Install yq
136-
run: |
137-
sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/v4.44.6/yq_linux_amd64
138-
sudo chmod +x /usr/local/bin/yq
139-
140117
- name: Discover latest tag
141118
id: discover
142119
uses: ./templates/gitops-ci/.github/actions/ghcr-latest-tag
143120
with:
144121
image: "aquasecurity/trivy"
145122
token: ${{ secrets.GITHUB_TOKEN }}
146123

147-
- name: Compare with demo compose
124+
- name: Compare with demo .env
148125
env:
149126
LATEST_TAG: ${{ steps.discover.outputs.tag }}
150127
run: |
151-
COMPOSE="demo/projects/demo-app/docker-compose.yaml"
128+
ENV_FILE="demo/projects/demo-app/.env"
152129
153-
backendImage=$(yq e '.services.backend.image' "$COMPOSE" | tr -d '"')
154-
currentTag=${backendImage##*:}
130+
source "${ENV_FILE}"
131+
currentTag="${BACKEND_TAG}"
155132
156133
echo "Current pinned: ${currentTag}"
157134
echo "Latest on GHCR: ${LATEST_TAG}"
@@ -161,11 +138,11 @@ jobs:
161138
else
162139
echo "Tags differ — a release PR would update ${currentTag} -> ${LATEST_TAG}"
163140
164-
# Show what the updated compose would look like
165-
cp "$COMPOSE" /tmp/compose-updated.yaml
166-
yq -i ".services.backend.image = \"ghcr.io/aquasecurity/trivy:${LATEST_TAG}\"" /tmp/compose-updated.yaml
167-
yq -i ".services.frontend.image = \"ghcr.io/aquasecurity/trivy:${LATEST_TAG}\"" /tmp/compose-updated.yaml
141+
# Show what the updated .env would look like
142+
cp "${ENV_FILE}" /tmp/env-updated
143+
sed -i "s/^BACKEND_TAG=.*/BACKEND_TAG=${LATEST_TAG}/" /tmp/env-updated
144+
sed -i "s/^FRONTEND_TAG=.*/FRONTEND_TAG=${LATEST_TAG}/" /tmp/env-updated
168145
169146
echo "--- Diff ---"
170-
diff "$COMPOSE" /tmp/compose-updated.yaml || true
147+
diff "${ENV_FILE}" /tmp/env-updated || true
171148
fi

.github/workflows/ci-pr-trigger.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ jobs:
5252
baseBranch: "main"
5353
backendTag: ${{ needs.get-versions.outputs.backend_tag }}
5454
frontendTag: ${{ needs.get-versions.outputs.frontend_tag }}
55-
composePath: "projects/demo-app/docker-compose.yaml"
55+
envPath: "projects/demo-app/.env"
5656
secrets: inherit
5757

5858
cleanup:

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# GitOps CI/CD Patterns
22

3-
Reusable GitHub Actions templates for automated GitOps deployments. Polls GHCR for new image tags, creates PRs to update `docker-compose.yaml`, and deploys on merge.
3+
Reusable GitHub Actions templates for automated GitOps deployments. Polls GHCR for new image tags, creates PRs to update a `.env` file (consumed by `docker-compose.yaml`), and deploys on merge.
44

55
## Structure
66

@@ -19,13 +19,14 @@ test/smoke.sh # Local validation script
1919
cp -r templates/gitops-ci/.github /path/to/your/repo/
2020
cp templates/gitops-ci/deployment.sh /path/to/your/repo/projects/<name>/
2121
cp templates/gitops-ci/docker-compose.yaml /path/to/your/repo/projects/<name>/
22+
cp templates/gitops-ci/.env /path/to/your/repo/projects/<name>/
2223
```
2324

2425
See the [template README](./templates/gitops-ci/README.md) for full setup instructions.
2526

2627
## Demo
2728

28-
The `demo/` directory is a working example using [`ghcr.io/aquasecurity/trivy`](https://github.com/aquasecurity/trivy/pkgs/container/trivy) — a public image with many semver tags. Backend pinned to `0.24.0`, frontend to `0.23.0` so tag discovery always finds newer versions.
29+
The `demo/` directory is a working example using [`ghcr.io/aquasecurity/trivy`](https://github.com/aquasecurity/trivy/pkgs/container/trivy) — a public image with many semver tags. Backend pinned to `0.24.0`, frontend to `0.23.0` in `.env` so tag discovery always finds newer versions.
2930

3031
The `.github/` directory runs the same demo workflows on this repo (cron disabled, deployment is echo-only). Trigger manually:
3132

@@ -49,7 +50,7 @@ CI runs automatically on push to main and PRs with 5 parallel validation jobs.
4950
## How It Works
5051

5152
1. **Trigger** — Polls GHCR on a schedule for the latest semver image tags
52-
2. **PR Creation** — Compares with current `docker-compose.yaml`, creates a PR if versions differ
53+
2. **PR Creation** — Compares with current `.env` file, creates a PR if versions differ
5354
3. **Deployment** — Merging the PR triggers deployment to the target server
5455

5556
Three deployment strategies: EC2/AWS, SSH, or self-hosted runner.

demo/.github/workflows/ci-create-release-pr.yaml

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# GitOps Release PR Creator (Reusable Workflow) — Demo
33
#
44
# Called by ci-pr-trigger.yaml. Compares new image tags against current
5-
# docker-compose.yaml and creates/updates a PR if versions differ.
5+
# .env file and creates/updates a PR if versions differ.
66
###############################################################################
77

88
name: "CI: Create release PR"
@@ -22,8 +22,8 @@ on:
2222
description: "New frontend image tag"
2323
required: false
2424
type: string
25-
composePath:
26-
description: "Path to docker-compose.yaml"
25+
envPath:
26+
description: "Path to .env file with image tags"
2727
required: true
2828
type: string
2929

@@ -35,22 +35,15 @@ jobs:
3535
with:
3636
ref: ${{ inputs.baseBranch }}
3737

38-
- name: Install yq
39-
uses: mikefarah/yq@v4.44.6
40-
4138
- name: Read current image tags
4239
id: current
4340
run: |
44-
backendImage=$(yq e '.services.backend.image' ${{ inputs.composePath }} | tr -d '"')
45-
frontendImage=$(yq e '.services.frontend.image' ${{ inputs.composePath }} | tr -d '"')
46-
47-
currentBackendTag=${backendImage##*:}
48-
currentFrontendTag=${frontendImage##*:}
41+
source ${{ inputs.envPath }}
4942
50-
echo "currentBackendTag=${currentBackendTag}" >> $GITHUB_OUTPUT
51-
echo "currentFrontendTag=${currentFrontendTag}" >> $GITHUB_OUTPUT
52-
echo "Current backend: ${currentBackendTag}"
53-
echo "Current frontend: ${currentFrontendTag}"
43+
echo "currentBackendTag=${BACKEND_TAG}" >> $GITHUB_OUTPUT
44+
echo "currentFrontendTag=${FRONTEND_TAG}" >> $GITHUB_OUTPUT
45+
echo "Current backend: ${BACKEND_TAG}"
46+
echo "Current frontend: ${FRONTEND_TAG}"
5447
5548
- name: Update image tags if changed
5649
id: update
@@ -59,19 +52,19 @@ jobs:
5952
FRONTEND_TAG: ${{ inputs.frontendTag }}
6053
CURRENT_BACKEND_TAG: ${{ steps.current.outputs.currentBackendTag }}
6154
CURRENT_FRONTEND_TAG: ${{ steps.current.outputs.currentFrontendTag }}
62-
COMPOSE_PATH: ${{ inputs.composePath }}
55+
ENV_PATH: ${{ inputs.envPath }}
6356
run: |
6457
changed=false
6558
6659
if [[ -n "${BACKEND_TAG}" && "${BACKEND_TAG}" != "${CURRENT_BACKEND_TAG}" ]]; then
6760
echo "Updating backend: ${CURRENT_BACKEND_TAG} -> ${BACKEND_TAG}"
68-
yq -i ".services.backend.image = \"ghcr.io/aquasecurity/trivy:${BACKEND_TAG}\"" "${COMPOSE_PATH}"
61+
sed -i "s/^BACKEND_TAG=.*/BACKEND_TAG=${BACKEND_TAG}/" "${ENV_PATH}"
6962
changed=true
7063
fi
7164
7265
if [[ -n "${FRONTEND_TAG}" && "${FRONTEND_TAG}" != "${CURRENT_FRONTEND_TAG}" ]]; then
7366
echo "Updating frontend: ${CURRENT_FRONTEND_TAG} -> ${FRONTEND_TAG}"
74-
yq -i ".services.frontend.image = \"ghcr.io/aquasecurity/trivy:${FRONTEND_TAG}\"" "${COMPOSE_PATH}"
67+
sed -i "s/^FRONTEND_TAG=.*/FRONTEND_TAG=${FRONTEND_TAG}/" "${ENV_PATH}"
7568
changed=true
7669
fi
7770

demo/.github/workflows/ci-deployment-self-hosted.yaml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,9 @@ jobs:
4848
- name: Get deployed versions
4949
id: versions
5050
run: |
51-
BACKEND=$(yq e '.services.backend.image' ${{ env.PROJECT_PATH }}/docker-compose.yaml | rev | cut -d: -f1 | rev)
52-
FRONTEND=$(yq e '.services.frontend.image' ${{ env.PROJECT_PATH }}/docker-compose.yaml | rev | cut -d: -f1 | rev)
53-
echo "backend=${BACKEND}" >> $GITHUB_OUTPUT
54-
echo "frontend=${FRONTEND}" >> $GITHUB_OUTPUT
51+
source ${{ env.PROJECT_PATH }}/.env
52+
echo "backend=${BACKEND_TAG}" >> $GITHUB_OUTPUT
53+
echo "frontend=${FRONTEND_TAG}" >> $GITHUB_OUTPUT
5554
5655
notify:
5756
needs: deploy

demo/.github/workflows/ci-pr-trigger.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ jobs:
5252
baseBranch: "main"
5353
backendTag: ${{ needs.get-versions.outputs.backend_tag }}
5454
frontendTag: ${{ needs.get-versions.outputs.frontend_tag }}
55-
composePath: "projects/demo-app/docker-compose.yaml"
55+
envPath: "projects/demo-app/.env"
5656
secrets: inherit
5757

5858
cleanup:

demo/README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ Uses [`ghcr.io/aquasecurity/trivy`](https://github.com/aquasecurity/trivy/pkgs/c
1818
| `{{REMOTE_PATH}}` | `/opt/demo-app` | Typical server deploy path |
1919
| `{{RUNNER_LABEL}}` | `demo-runner` | Self-hosted runner label |
2020

21-
## Pinned Versions
21+
## Pinned Versions (in `.env`)
2222

23-
- Backend: `0.24.0`
24-
- Frontend: `0.23.0`
23+
- `BACKEND_TAG=0.24.0`
24+
- `FRONTEND_TAG=0.23.0`
2525

2626
These are old enough that the GHCR tag discovery will always find newer versions, exercising the full PR-creation flow.
2727

@@ -39,6 +39,7 @@ demo/
3939
ci-deployment-self-hosted.yaml # Deploys via self-hosted runner
4040
projects/
4141
demo-app/
42-
docker-compose.yaml # Pinned to trivy 0.24.0 / 0.23.0
42+
.env # Pinned to trivy 0.24.0 / 0.23.0
43+
docker-compose.yaml # References ${BACKEND_TAG} / ${FRONTEND_TAG} from .env
4344
deployment.sh # Standard deployment script
4445
```

0 commit comments

Comments
 (0)