Env Variables in EC2 - VittorioDeMarzi/hero-beans GitHub Wiki

There are two primary methods for managing environment variables within the context of CodePipeline and CodeDeploy:

  • Method 1 (Simpler): Include an environment-specific .env file in the build artifact.
  • Method 2 (More Secure & Flexible But in this moment we don't have access to it): Use AWS Systems Manager (SSM) Parameter Store to manage variables and retrieve them during deployment.

Method 1: Include a .env File in the Artifact

This method is quick and easy. The idea is to instruct the CodeBuild stage to create a .env file with the correct values before packaging the artifact that will be sent to CodeDeploy.

Step 1: Add Environment Variables in CodeBuild

  1. Go to your pipeline in CodePipeline, find your Build stage, and click Edit.
  2. Click on your CodeBuild project name to open it in a new tab.
  3. In the CodeBuild console, go to Edit > Environment.
  4. Scroll down to the Additional configuration > Environment variables section.
  5. Add your variables here. For example:
    • Name: DB_HOST
    • Value: prod.database.endpoint.com
    • Type: Plaintext (or Parameter Store if they are already in SSM).
    • Name: API_KEY
    • Value: your-secret-api-key
    • Type: Secrets Manager or Parameter Store (recommended for secrets).

Step 2: Modify the buildspec.yml

Now, instruct CodeBuild on how to use these variables to create a .env file. Add these commands to the build or pre_build section of the buildspec.yml file:

version: 0.2

phases:
  build:
    commands:
      - echo "Building the project..."
      # Create the .env file using CodeBuild's environment variables
      - echo "DB_HOST=${DB_HOST}" > .env
      - echo "DB_USER=${DB_USER}" >> .env
      - echo "API_KEY=${API_KEY}" >> .env
      # ...other build commands...
artifacts:
  files:
    - '**/*' # Includes all files, including the new .env file

New buildspec.yml

version: 0.2

phases:
  install:
    runtime-versions:
      java: corretto21
  pre_build:
    commands:
      - echo ">>> Cleaning project..."
      - ./gradlew clean

      - echo ">>> Creating .env file..."
      - echo "DB_HOST=${DB_HOST}" > .env
      - echo "DB_USER=${DB_USER}" >> .env
      - echo "API_KEY=${API_KEY}" >> .env
      - echo ".env file created:"
      - cat .env 

  build:
    commands:
      - echo ">>> Building jar..."
      - ./gradlew bootJar --no-daemon
  post_build:
    commands:
      - echo ">>> Build completed. Packaging for deployment..."

artifacts:
  files:
    - .env
    - build/libs/*.jar
    - appspec.yml
    - scripts/**/*
  discard-paths: no

How it works:

  • CodeBuild reads the environment variables you configured.
  • The command echo "DB_HOST=${DB_HOST}" > .env creates the .env file and writes the first variable to it.
  • The subsequent commands using >> append the other variables to the same file.
  • The final artifact will contain the code and the ready-to-use .env file.
  • CodeDeploy will then copy it to the EC2 instance along with everything else.

Now we need to tell the start_server.sh script to load the variables from the .env file into the shell's environment before it runs the Java application.

A Spring Boot application automatically reads environment variables from the system, but it doesn't read .env files by default. The script acts as the bridge.


Updated start_server.sh Script

#!/bin/bash
set -euo pipefail

# Define the path where the application is deployed.
# This should match the 'destination' in the appspec.yml file.
APP_DIR="/home/ubuntu/app"
ENV_FILE="$APP_DIR/.env"

# Check if the .env file exists and load it.
if [ -f "$ENV_FILE" ]; then
  echo ">>> [ApplicationStart] Loading environment variables from $ENV_FILE"
  # The 'set -a' command automatically exports all variables defined in the sourced file.
  set -a
  source "$ENV_FILE"
  set +a
else
  echo ">>> [ApplicationStart] Warning: .env file not found. Proceeding with existing environment variables."
fi


JAR_FILE=$(ls $APP_DIR/build/libs/*.jar | head -n 1 || true)

PROFILE="${1:-${SPRING_PROFILES_ACTIVE:-prod}}"

SPRING_OPTS="--spring.profiles.active=${PROFILE} --logging.config=classpath:logback-spring.xml"

if [ -z "$JAR_FILE" ]; then
  echo ">>> [ApplicationStart] No JAR file found!"
  exit 1
fi

echo ">>> [ApplicationStart] Starting: $JAR_FILE (profile=${PROFILE})"
# The Java process will now inherit the variables loaded from the .env file.
nohup java $JAVA_OPTS -jar "$JAR_FILE" $SPRING_OPTS > /dev/null 2>&1 &

What's Changed and Why

  1. APP_DIR Variable: I've added a variable for the application directory (/home/ubuntu/app). This makes the script easier to read and modify if you ever change the deployment destination in the appspec.yml.
  2. Check for .env File: The if [ -f "$ENV_FILE" ]; then block checks if the .env file actually exists at the specified path.
  3. set -a and source: This is the key part.
    • source "$ENV_FILE" reads the .env file and defines the variables (e.g., DB_HOST=...) in the current shell.
    • set -a (short for allexport) ensures that these variables are exported, making them available to any child processes, which in this case is the java -jar command.
    • set +a disables the allexport setting afterward.
  4. Updated JAR_FILE Path: I updated the path to use the $APP_DIR variable for consistency.

Now, when CodeDeploy runs this script, it will first load the configuration from the .env file, and then the Spring Boot application will start up with all the correct settings.