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: 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