name: Docker Publish (Release) on: release: types: [published] workflow_dispatch: inputs: version: description: "Version tag to publish (e.g. 0.1.2 or v0.1.2)" required: false type: string jobs: publish: name: Build and Push Docker Images runs-on: ubuntu-latest permissions: contents: read id-token: write attestations: write env: # Optional repo variable. If unset, DOCKERHUB_USERNAME is used. IMAGE_NAMESPACE: ${{ vars.DOCKERHUB_NAMESPACE }} steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.13" - name: Dependency security gate (pip-audit) run: | python -m pip install --upgrade pip pip install pip-audit pip-audit -r backend/requirements.txt --format json --aliases --output pip-audit-backend.json || true python backend/scripts/pip_audit_gate.py \ --report pip-audit-backend.json \ --allowlist ops/security/pip-audit-allowlist.json - name: Resolve version/tag id: ver shell: bash run: | RAW_TAG="${{ github.event.release.tag_name }}" if [ -z "$RAW_TAG" ]; then RAW_TAG="${{ inputs.version }}" fi if [ -z "$RAW_TAG" ]; then RAW_TAG="${GITHUB_REF_NAME}" fi CLEAN_TAG="${RAW_TAG#v}" echo "raw=$RAW_TAG" >> "$GITHUB_OUTPUT" echo "clean=$CLEAN_TAG" >> "$GITHUB_OUTPUT" - name: Set image namespace id: ns shell: bash run: | NS="${IMAGE_NAMESPACE}" if [ -z "$NS" ]; then NS="${{ secrets.DOCKERHUB_USERNAME }}" fi # Normalize accidental input like spaces or uppercase. NS="$(echo "$NS" | tr '[:upper:]' '[:lower:]' | xargs)" # Reject clearly invalid placeholders/config mistakes early. if [ -z "$NS" ] || [ "$NS" = "-" ]; then echo "Missing Docker Hub namespace. Set repo var DOCKERHUB_NAMESPACE or secret DOCKERHUB_USERNAME." exit 1 fi # Namespace must be a single Docker Hub account/org name, not a path/url. if [[ "$NS" == *"/"* ]] || [[ "$NS" == *":"* ]]; then echo "Invalid Docker Hub namespace '$NS'. Use only the account/org name (e.g. 'nesterovicit')." exit 1 fi if ! [[ "$NS" =~ ^[a-z0-9]+([._-][a-z0-9]+)*$ ]]; then echo "Invalid Docker Hub namespace '$NS'. Allowed: lowercase letters, digits, ., _, -" exit 1 fi echo "Using Docker Hub namespace: $NS" echo "value=$NS" >> "$GITHUB_OUTPUT" - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push backend image uses: docker/build-push-action@v6 with: context: ./backend file: ./backend/Dockerfile push: true provenance: mode=max sbom: true labels: | org.opencontainers.image.title=NexaPG Backend org.opencontainers.image.vendor=Nesterovic IT-Services e.U. org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} org.opencontainers.image.version=${{ steps.ver.outputs.clean }} tags: | ${{ steps.ns.outputs.value }}/nexapg-backend:${{ steps.ver.outputs.clean }} ${{ steps.ns.outputs.value }}/nexapg-backend:latest cache-from: type=registry,ref=${{ steps.ns.outputs.value }}/nexapg-backend:buildcache cache-to: type=registry,ref=${{ steps.ns.outputs.value }}/nexapg-backend:buildcache,mode=max - name: Build and push frontend image uses: docker/build-push-action@v6 with: context: ./frontend file: ./frontend/Dockerfile push: true provenance: mode=max sbom: true build-args: | VITE_API_URL=/api/v1 labels: | org.opencontainers.image.title=NexaPG Frontend org.opencontainers.image.vendor=Nesterovic IT-Services e.U. org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} org.opencontainers.image.version=${{ steps.ver.outputs.clean }} tags: | ${{ steps.ns.outputs.value }}/nexapg-frontend:${{ steps.ver.outputs.clean }} ${{ steps.ns.outputs.value }}/nexapg-frontend:latest cache-from: type=registry,ref=${{ steps.ns.outputs.value }}/nexapg-frontend:buildcache cache-to: type=registry,ref=${{ steps.ns.outputs.value }}/nexapg-frontend:buildcache,mode=max