diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e7864b1..9b310eb 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -15,7 +15,7 @@ updates: open-pull-requests-limit: 1 - package-ecosystem: "docker" - directory: "/dockerfiles/" + directory: "/" schedule: interval: "daily" open-pull-requests-limit: 1 diff --git a/.github/workflows/build-native-image.yml b/.github/workflows/build-native.yml similarity index 62% rename from .github/workflows/build-native-image.yml rename to .github/workflows/build-native.yml index 708a63a..fe37c81 100644 --- a/.github/workflows/build-native-image.yml +++ b/.github/workflows/build-native.yml @@ -1,17 +1,25 @@ -name: build native image +name: build native on: push: branches: [ 'master', 'feature/**' ] tags: [ 'v*.*.*' ] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: + APP_NAME: bgg-api-native DOCKER_REGISTRY: ghcr.io DOCKER_IMAGE: ${{ github.repository }}-native + DOCKER_FILE: Dockerfile.native + DOCKER_COMPOSE_FILE: docker-compose.native.yml jobs: build: runs-on: ubuntu-latest + timeout-minutes: 15 permissions: contents: read packages: write @@ -22,35 +30,34 @@ jobs: - name: Checkout project uses: actions/checkout@v6 - - name: Set up GraalVM + - name: Set up JDK and Maven uses: graalvm/setup-graalvm@v1 with: distribution: graalvm java-version: 25 cache: maven - - name: Build artefacts + - name: Build with Maven run: | mvn --batch-mode native:compile -Pnative - # build image - - - name: Build image + - name: Build Docker image uses: docker/build-push-action@v7 with: context: . - file: dockerfiles/Dockerfile.native + file: ${{ env.DOCKER_FILE }} load: true - tags: ${{ env.DOCKER_IMAGE }}:${{ github.sha }} + tags: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:master - - name: Test image + - name: Test Docker image + env: + BGG_APPLICATION_TOKEN: dummy run: | - DOCKER_PID=$(docker run -d -p 8080:8080 --rm ${{ env.DOCKER_IMAGE }}:${{ github.sha }} 2>/dev/null) - sleep 2s - curl --fail http://localhost:8080/bgg-api/actuator/health - docker stop $DOCKER_PID + trap 'docker compose -f ${{ env.DOCKER_COMPOSE_FILE }} down --remove-orphans' EXIT + docker compose -f ${{ env.DOCKER_COMPOSE_FILE }} up -d --wait --wait-timeout 60 + docker compose -f ${{ env.DOCKER_COMPOSE_FILE }} ps --format json | grep -q '"Health":"healthy"' - # deploy + # publish - name: Login to Docker registry if: github.ref == 'refs/heads/master' @@ -67,12 +74,12 @@ jobs: with: images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }} - - name: Deploy image + - name: Publish Docker image if: github.ref == 'refs/heads/master' uses: docker/build-push-action@v7 with: context: . - file: dockerfiles/Dockerfile.native + file: ${{ env.DOCKER_FILE }} push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} @@ -83,13 +90,13 @@ jobs: if: github.ref == 'refs/heads/master' environment: name: native-staging - url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}/bgg-api/actuator/health + url: ${{ steps.deploy-to-azure-webapp.outputs.webapp-url }}/bgg-api/actuator/health steps: - name: Deploy to Azure Web App - id: deploy-to-webapp + id: deploy-to-azure-webapp uses: azure/webapps-deploy@v3 with: - app-name: bgg-api-native-staging + app-name: ${{ env.APP_NAME }}-staging publish-profile: ${{ secrets.AZURE_PUBLISH_PROFILE_NATIVE_STAGING }} images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:master diff --git a/.github/workflows/build-image.yml b/.github/workflows/build.yml similarity index 62% rename from .github/workflows/build-image.yml rename to .github/workflows/build.yml index d555aad..051bac3 100644 --- a/.github/workflows/build-image.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: build image +name: build on: push: @@ -7,14 +7,22 @@ on: pull_request: branches: [ 'master' ] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: + APP_NAME: bgg-api DOCKER_REGISTRY: ghcr.io DOCKER_IMAGE: ${{ github.repository }} + DOCKER_FILE: Dockerfile + DOCKER_COMPOSE_FILE: docker-compose.yml GITHUB_PACKAGE_URL: https://maven.pkg.github.com/${{ github.repository }} jobs: build: runs-on: ubuntu-latest + timeout-minutes: 15 permissions: contents: read packages: write @@ -25,40 +33,39 @@ jobs: - name: Checkout project uses: actions/checkout@v6 - - name: Set up Java + - name: Set up JDK and Maven uses: actions/setup-java@v5 with: distribution: temurin java-version: 25 cache: maven - - name: Build artefacts + - name: Build with Maven run: | mvn --batch-mode verify - # build image - - - name: Build image + - name: Build Docker image uses: docker/build-push-action@v7 with: context: . - file: dockerfiles/Dockerfile + file: ${{ env.DOCKER_FILE }} load: true - tags: ${{ env.DOCKER_IMAGE }}:${{ github.sha }} + tags: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:master - - name: Test image + - name: Test Docker image + env: + BGG_APPLICATION_TOKEN: dummy run: | - DOCKER_PID=$(docker run -d -p 8080:8080 --rm ${{ env.DOCKER_IMAGE }}:${{ github.sha }} 2>/dev/null) - sleep 10s - curl --fail http://localhost:8080/bgg-api/actuator/health - docker stop $DOCKER_PID + trap 'docker compose -f ${{ env.DOCKER_COMPOSE_FILE }} down --remove-orphans' EXIT + docker compose -f ${{ env.DOCKER_COMPOSE_FILE }} up -d --wait --wait-timeout 60 + docker compose -f ${{ env.DOCKER_COMPOSE_FILE }} ps --format json | grep -q '"Health":"healthy"' - # deploy + # publish - - name: Deploy artefacts + - name: Publish Maven artifacts if: github.ref == 'refs/heads/master' run: | - mvn --batch-mode validate jar:jar deploy:deploy -DaltDeploymentRepository=github::default::${{ env.GITHUB_PACKAGE_URL }} + mvn --batch-mode deploy:deploy -DaltDeploymentRepository=github::default::${{ env.GITHUB_PACKAGE_URL }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -77,12 +84,12 @@ jobs: with: images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }} - - name: Deploy image + - name: Publish Docker image if: github.ref == 'refs/heads/master' uses: docker/build-push-action@v7 with: context: . - file: dockerfiles/Dockerfile + file: ${{ env.DOCKER_FILE }} push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} @@ -93,13 +100,13 @@ jobs: if: github.ref == 'refs/heads/master' environment: name: staging - url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}/bgg-api/actuator/health + url: ${{ steps.deploy-to-azure-webapp.outputs.webapp-url }}/bgg-api/actuator/health steps: - name: Deploy to Azure Web App - id: deploy-to-webapp + id: deploy-to-azure-webapp uses: azure/webapps-deploy@v3 with: - app-name: bgg-api-staging + app-name: ${{ env.APP_NAME }}-staging publish-profile: ${{ secrets.AZURE_PUBLISH_PROFILE_STAGING }} images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:master diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ac5a60a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +FROM gcr.io/distroless/java25-debian13:latest + +# Run as a non-root user with a well-known UID (65534 = nobody) +USER 65534:65534 + +WORKDIR /app + +# Copy static wget from busybox +COPY --from=busybox:uclibc /bin/wget /usr/local/bin/wget + +COPY target/bgg-api*.jar bgg-api.jar + +EXPOSE 8080 + +HEALTHCHECK \ + --interval=30s \ + --timeout=5s \ + --start-period=10s \ + --retries=3 \ + CMD [ "wget", "-qO-", "http://localhost:8080/bgg-api/actuator/health" ] + +ENTRYPOINT [ "java", "-jar", "bgg-api.jar" ] diff --git a/Dockerfile.native b/Dockerfile.native new file mode 100644 index 0000000..ba2c19d --- /dev/null +++ b/Dockerfile.native @@ -0,0 +1,22 @@ +FROM gcr.io/distroless/java25-debian13:latest + +# Run as a non-root user with a well-known UID (65534 = nobody) +USER 65534:65534 + +WORKDIR /app + +# Copy static wget from busybox +COPY --from=busybox:uclibc /bin/wget /usr/local/bin/wget + +COPY target/bgg-api bgg-api + +EXPOSE 8080 + +HEALTHCHECK \ + --interval=30s \ + --timeout=5s \ + --start-period=5s \ + --retries=3 \ + CMD [ "wget", "-qO-", "http://localhost:8080/bgg-api/actuator/health" ] + +ENTRYPOINT [ "./bgg-api" ] diff --git a/docker-compose.native.yml b/docker-compose.native.yml new file mode 100644 index 0000000..fa18ea8 --- /dev/null +++ b/docker-compose.native.yml @@ -0,0 +1,20 @@ +name: bgg-api-native + +services: + bgg-api-native-backend: + image: ghcr.io/tnaskali/bgg-api-native:master + build: + context: . + dockerfile: Dockerfile.native + container_name: bgg-api-native-backend + ports: + - "8080:8080" + environment: + SERVER_PORT: 8080 + BGG_APPLICATION_TOKEN: ${BGG_APPLICATION_TOKEN} + restart: unless-stopped + deploy: + restart_policy: + condition: on-failure + delay: 10s + max_attempts: 3 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..635c1b4 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,19 @@ +name: bgg-api + +services: + bgg-api-backend: + image: ghcr.io/tnaskali/bgg-api:master + build: + context: . + container_name: bgg-api-backend + ports: + - "8080:8080" + environment: + SERVER_PORT: 8080 + BGG_APPLICATION_TOKEN: ${BGG_APPLICATION_TOKEN} + restart: unless-stopped + deploy: + restart_policy: + condition: on-failure + delay: 10s + max_attempts: 3 diff --git a/dockerfiles/Dockerfile b/dockerfiles/Dockerfile deleted file mode 100644 index fcb8de9..0000000 --- a/dockerfiles/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM gcr.io/distroless/java25-debian13:latest - -ENV SERVER_PORT=8080 -EXPOSE ${SERVER_PORT} - -WORKDIR /opt/bgg-api - -# Run as a non-root user with a well-known UID (65534 = nobody). -# Distroless has no shell, so we use a pre-existing system user. -USER 65534:65534 - -ENTRYPOINT ["java","-jar","./bgg-api.jar"] - -# Copy the JAR last — this layer changes most frequently, -# so it goes at the end to preserve caching for all previous layers. -COPY target/bgg-api*.jar /opt/bgg-api/bgg-api.jar diff --git a/dockerfiles/Dockerfile.native b/dockerfiles/Dockerfile.native deleted file mode 100644 index 3c833f8..0000000 --- a/dockerfiles/Dockerfile.native +++ /dev/null @@ -1,16 +0,0 @@ -FROM gcr.io/distroless/base-debian13:latest - -ENV SERVER_PORT=8080 -EXPOSE ${SERVER_PORT} - -WORKDIR /opt/bgg-api - -# Run as a non-root user with a well-known UID (65534 = nobody). -# distroless/base has no shell, so we use a pre-existing system user. -USER 65534:65534 - -ENTRYPOINT ["./bgg-api"] - -# Copy the binary last — this layer changes most frequently, -# so it goes at the end to preserve caching for all previous layers. -COPY target/bgg-api /opt/bgg-api/bgg-api