Skip to content

feat: migrate to GraalVM native image as default deployment#104

Merged
vitorhugo-java merged 1 commit into
masterfrom
copilot/update-espacogeek-native-image
Apr 15, 2026
Merged

feat: migrate to GraalVM native image as default deployment#104
vitorhugo-java merged 1 commit into
masterfrom
copilot/update-espacogeek-native-image

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 15, 2026

Summary

Migrates the EspacoGeek deployment pipeline to use GraalVM native image as the active default. The existing JVM pipeline is preserved but disabled so it can be re-enabled easily.

Changes

.github/workflows/cicd.yml

  • Disabled the build_and_publish (JVM) job with if: false — fully preserved, just skipped
  • Updated the build_native_and_publish job's push step to tag and push the image as ghcr.io/<owner>/espacogeek-api:native (single :native tag instead of multi-tag with -native image-name suffix)
  • Added an SSH deploy step to the native job (mirroring the JVM job's deploy step) that calls the deploy script with "native" as the image tag

docker/docker-compose.yml

  • Renamed the service from app-jvm to springapi
  • Removed the build: block (native image is pulled from GHCR, not built locally)
  • Switched the active image: to espacogeek-api:native; the previous espacogeek-jvm:latest reference is kept commented out directly above it
  • Commented out JAVA_OPTS (not applicable to native images)
  • Removed the JVM debug port 5005:5005
  • Reduced memory limits from 3500M/2000M to 512M/256M (appropriate for native images)
  • Fixed the networks: top-level key (was missing, causing infra_network to appear at the wrong YAML level)

docker/deploy.sh

  • Changed the default IMAGE_TAG from latest to native so standalone invocations default to the native image
  • Standardized the backup directory name to espacogeek-api-backups (consistent with the embedded script in the CI workflow)

Copilot AI requested a review from vitorhugo-java April 15, 2026 11:45
@vitorhugo-java vitorhugo-java marked this pull request as ready for review April 15, 2026 11:47
Copilot AI review requested due to automatic review settings April 15, 2026 11:47
@vitorhugo-java vitorhugo-java merged commit 338bd7e into master Apr 15, 2026
5 checks passed
@vitorhugo-java vitorhugo-java deleted the copilot/update-espacogeek-native-image branch April 15, 2026 11:49
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Migrates the deployment pipeline to make the GraalVM native-image container the default deployment artifact, while preserving (but disabling) the JVM pipeline.

Changes:

  • Disabled the JVM build/publish job and shifted default deployment to the native-image job.
  • Updated native image publishing to a single :native tag and added SSH-based deployment for the native pipeline.
  • Updated Docker Compose service configuration for running the native image and corrected the top-level networks key; updated deploy script default tag to native.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.

File Description
.github/workflows/cicd.yml Disables JVM pipeline, changes native image tagging/pushing, adds SSH deploy flow for native image.
docker/docker-compose.yml Switches service to use the native image, updates resource limits/ports, fixes networks nesting.
docker/deploy.sh Defaults standalone deploys to IMAGE_TAG=native.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docker/docker-compose.yml
container_name: espacogeek-jvm
springapi:
#image: espacogeek-jvm:latest
image: espacogeek-api:native
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image: espacogeek-api:native is an unqualified image reference, so Compose will try to pull from Docker Hub (or rely on a locally-tagged image). If the intent is to pull the native image from GHCR (as described in the PR), update this to a fully-qualified ghcr.io/<owner>/<app>:native reference (optionally via env vars) so deployments are deterministic.

Suggested change
image: espacogeek-api:native
image: ${SPRINGAPI_IMAGE:-ghcr.io/espacogeek/espacogeek-api:native}

Copilot uses AI. Check for mistakes.
Comment on lines 595 to +603
- name: Tag and push native Docker image
if: steps.vars.outputs.should_push == 'true' && github.event_name == 'push'
shell: bash
run: |
set -euo pipefail
IFS=',' read -ra TAGS <<< "${{ steps.vars.outputs.tags_csv }}"
for tag in "${TAGS[@]}"; do
FULL_TAG="ghcr.io/${{ env.GHCR_OWNER_LC }}/${{ env.APP_NAME }}-native:${tag}"
docker tag native-validation:ci "${FULL_TAG}"
docker push "${FULL_TAG}"
echo "Pushed: ${FULL_TAG}"
done
FULL_TAG="ghcr.io/${{ env.GHCR_OWNER_LC }}/${{ env.APP_NAME }}:native"
docker tag native-validation:ci "${FULL_TAG}"
docker push "${FULL_TAG}"
echo "Pushed: ${FULL_TAG}"
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Tag and push native Docker image step only runs on push, but steps.vars.outputs.should_push is also true for workflow_dispatch. That means a manually-dispatched run will build native-validation:ci but never push :native, and the subsequent SSH deploy will pull whatever image is currently in GHCR (not the one just built). Consider either allowing this step to run on workflow_dispatch too, or adjusting should_push/deploy conditions so manual runs don't build without publishing.

Copilot uses AI. Check for mistakes.
Comment on lines +614 to +621
script_stop: false
script: |
cleanup_on_exit() {
echo "Running SSH-level cleanup..."
rm -f .env.espacogeek 2>/dev/null || true
docker logout ghcr.io 2>/dev/null || true
}
trap cleanup_on_exit EXIT INT TERM
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This SSH deploy step sets script_stop: false and the script body doesn't use set -e/explicit error handling, so a failing deploy command can be followed by successful cleanup commands and still return exit code 0 (making the workflow appear green even though the deploy failed). To ensure deploy failures fail the workflow, enable script_stop: true and/or add set -euo pipefail at the start of the SSH script and guard cleanup commands with || true.

Copilot uses AI. Check for mistakes.
Comment on lines +767 to +783
cat > .env.espacogeek << ENVEOF
SPRING_DATASOURCE_URL=${{ secrets.SPRING_DATASOURCE_URL }}
SPRING_DATASOURCE_USERNAME=${{ secrets.SPRING_DATASOURCE_USERNAME }}
SPRING_DATASOURCE_PASSWORD=${{ secrets.SPRING_DATASOURCE_PASSWORD }}
SPRING_MVC_CORS_ALLOWED_ORIGINS=${{ secrets.SPRING_MVC_CORS_ALLOWED_ORIGINS }}
SECURITY_JWT_ISSUER=${{ secrets.SECURITY_JWT_ISSUER }}
SECURITY_JWT_EXPIRATION_MS=${{ secrets.SECURITY_JWT_EXPIRATION_MS }}
SECURITY_JWT_SECRET=${{ secrets.SECURITY_JWT_SECRET }}
SAMESITE_WHEN_SAME_SITE=${{ secrets.SAMESITE_WHEN_SAME_SITE }}
SECURITY_CSRF_COOKIE_DOMAIN=${{ secrets.SECURITY_CSRF_COOKIE_DOMAIN }}
SECURITY_CSRF_COOKIE_SAME_SITE=${{ secrets.SECURITY_CSRF_COOKIE_SAME_SITE }}
MAIL_HOST=${{ secrets.MAIL_HOST }}
MAIL_PORT=${{ secrets.MAIL_PORT }}
MAIL_USERNAME=${{ secrets.MAIL_USERNAME }}
MAIL_PASSWORD=${{ secrets.MAIL_PASSWORD }}
FRONTEND_URL=${{ secrets.FRONTEND_URL }}
ENVEOF
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The .env.espacogeek file is written with production secrets but its permissions are not restricted. On multi-user hosts or with a permissive umask, this file may be readable by other users/processes. Set a restrictive umask (e.g. umask 077) before creating it and/or chmod 600 .env.espacogeek immediately after writing.

Copilot uses AI. Check for mistakes.
Comment on lines +785 to +804
echo "Validating environment file..."
if ! grep -q "^SPRING_DATASOURCE_URL=" .env.espacogeek; then
echo "ERROR: SPRING_DATASOURCE_URL not found in .env.espacogeek"
cat .env.espacogeek
rm -f .env.espacogeek
exit 1
fi
DATASOURCE_URL=$(grep "^SPRING_DATASOURCE_URL=" .env.espacogeek | cut -d'=' -f2)
if [ -z "$DATASOURCE_URL" ]; then
echo "ERROR: SPRING_DATASOURCE_URL is empty"
rm -f .env.espacogeek
exit 1
fi
if [[ ! "$DATASOURCE_URL" =~ ^jdbc: ]]; then
echo "ERROR: SPRING_DATASOURCE_URL must start with 'jdbc:' but got: $DATASOURCE_URL"
rm -f .env.espacogeek
exit 1
fi
echo "✓ SPRING_DATASOURCE_URL is valid: $DATASOURCE_URL"

Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This validation block can leak secrets into GitHub Actions logs: it cats the full .env.espacogeek on error and also echoes the datasource URL value. Since the env file contains DB credentials/JWT secrets, remove the cat and avoid printing secret values (log only that a key is present/has correct prefix, not the value).

Copilot uses AI. Check for mistakes.
Comment on lines +646 to +665
CONTAINER_NAME="espacogeek-api"
BACKUP_DIR="${HOME}/espacogeek-api-backups"
OLD_CONTAINER_BACKUP="${CONTAINER_NAME}-old"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

cleanup_env_file() {
if [ -f "$ENV_FILE" ]; then
log_info "Cleaning up environment file..."
rm -f "$ENV_FILE"
fi
}
trap cleanup_env_file EXIT

if [ -z "$GHCR_OWNER_LC" ] || [ -z "$APP_NAME" ]; then
log_error "Usage: $0 <GHCR_OWNER_LC> <APP_NAME> [IMAGE_TAG] [ENV_FILE]"
exit 1
fi

IMAGE="ghcr.io/${GHCR_OWNER_LC}/${APP_NAME}:${IMAGE_TAG}"

Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workflow now embeds a full deploy script via heredoc even though docker/deploy.sh exists in the repo and is also being modified in this PR. Keeping two different deploy implementations (with different health/rollback behavior) increases the risk of drift. Consider reusing the repo script (e.g., copy it to the host and execute it) or sourcing a single canonical script for both JVM and native deployments.

Suggested change
CONTAINER_NAME="espacogeek-api"
BACKUP_DIR="${HOME}/espacogeek-api-backups"
OLD_CONTAINER_BACKUP="${CONTAINER_NAME}-old"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
cleanup_env_file() {
if [ -f "$ENV_FILE" ]; then
log_info "Cleaning up environment file..."
rm -f "$ENV_FILE"
fi
}
trap cleanup_env_file EXIT
if [ -z "$GHCR_OWNER_LC" ] || [ -z "$APP_NAME" ]; then
log_error "Usage: $0 <GHCR_OWNER_LC> <APP_NAME> [IMAGE_TAG] [ENV_FILE]"
exit 1
fi
IMAGE="ghcr.io/${GHCR_OWNER_LC}/${APP_NAME}:${IMAGE_TAG}"
DEPLOY_SCRIPT="docker/deploy.sh"
if [ -z "$GHCR_OWNER_LC" ] || [ -z "$APP_NAME" ]; then
log_error "Usage: $0 <GHCR_OWNER_LC> <APP_NAME> [IMAGE_TAG] [ENV_FILE]"
exit 1
fi
if [ ! -f "$DEPLOY_SCRIPT" ]; then
log_error "Canonical deploy script not found: $DEPLOY_SCRIPT"
exit 1
fi
chmod +x "$DEPLOY_SCRIPT"
log_info "Delegating deployment to ${DEPLOY_SCRIPT}..."
exec bash "$DEPLOY_SCRIPT" "$GHCR_OWNER_LC" "$APP_NAME" "$IMAGE_TAG" "$ENV_FILE"

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants