Create a Secure, Buildable Application and a CI Workflow
Create the Sample Application and Dockerfile
First, we need something to build. We’ll create a very simple Node.js “Hello World” application and a security-conscious Dockerfile.
In your
secure-container-pipeline
project directory, create a new folder namedapp
.mkdir app cd app
Create the Node.js application file
app.js
:# In the 'app' directory touch app.js
Paste this simple server code into
app/app.js
:// app/app.js const http = require("http"); const port = 8080; const server = http.createServer((req, res) => { res.statusCode = 200; res.setHeader("Content-Type", "text/plain"); res.end("Hello, FCJ-ers!\n"); }); server.listen(port, () => { console.log(`Server running on port ${port}`); });
Create the
Dockerfile
:# app/Dockerfile # Stage 1: Use a specific, slim base image to reduce attack surface. FROM node:22-slim AS base # Create a dedicated, non-root user and group for the application. # This is a critical security measure. RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 appuser WORKDIR /home/appuser/app # Copy only the necessary file and set correct permissions. COPY --chown=appuser:nodejs app.js . # Switch to the non-root user. Any subsequent commands run as this user. USER appuser # Expose the port the app runs on. EXPOSE 8080 # Command to run the application. CMD [ "node", "app.js" ]
Go back to the root of your project directory:
cd ..
Create the ECR Repository
Let’s create the secure container registry where we’ll store our Docker images.
Run this AWS CLI command in your terminal:
aws ecr create-repository \ --repository-name workshop-app \ --image-scanning-configuration scanOnPush=true \ --region us-east-2 # Use the same region as your cluster
Info
The
--image-scanning-configuration scanOnPush=true
flag is our first deliberate security control. We’ve instructed AWS to automatically scan every new image we push to this repository for known vulnerabilities (CVEs). This is a foundational piece of our secure pipeline.
Set Up Secure Access from GitHub Actions to AWS (OIDC)
We need to grant GitHub the permission to push images to our ECR repository. We will use the modern, secure, passwordless method: OIDC (OpenID Connect).
- In the AWS Console, go to IAM → Identity providers.
- Click Add provider.
- Select OpenID Connect.
- For Provider URL, enter
https://token.actions.githubusercontent.com
. - For Audience, enter
sts.amazonaws.com
. - Click Add provider.
- Create the IAM Role for GitHub Actions.
- Go to IAM → Roles → Create role.
- For Trusted entity type, select Web identity.
- From the Identity provider dropdown, select the
token.actions.githubusercontent.com
provider you just created. - For Audience, select
sts.amazonaws.com
. - For GitHub organization/repository, enter your details. For a personal project, you can be specific:
- Organization:
your-github-username
- Repository:
secure-container-pipeline
- (Optional but recommended) Branch:
main
ormaster
- Organization:
- Click Next.
- On the Add permissions screen, find and attach the
AmazonEC2ContainerRegistryPowerUser
policy. This gives just enough permission to log in and push images to ECR. - Click Next.
- Give the role a name, like
WorkshopGitHubActionsRole
(Remember the role name. You will use this role to deploy to the EKS cluster later) - Create the role.
- CRITICAL: Click on the new role you just created and copy its ARN. It will look like
arn:aws:iam::<<AWS Account ID>>:role/WorkshopGitHubActionsRole
. You will need this for the next step.
Create the GitHub Actions CI Workflow
This is the heart of our automated build and scan process.
Create the workflow directory structure:
mkdir -p .github/workflows
Create the workflow file
ci.yml
:touch .github/workflows/ci.yml
Paste the following YAML into
.github/workflows/ci.yml
. Replace the placeholder with your actual Role ARN.# .github/workflows/ci.yml name: CI Workflow for EKS Workshop # This workflow runs on any push to the main branch on: push: branches: [main] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: env: AWS_REGION: us-east-2 # Your AWS region ECR_REPOSITORY: workshop-app # Your ECR repository name EKS_CLUSTER_NAME: workshop-cluster # Your EKS cluster name jobs: build-scan-push: name: Build, Scan & Push runs-on: ubuntu-latest outputs: image: ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ steps.image-def.outputs.tag }} permissions: # Required for OIDC connection to AWS id-token: write contents: read steps: - name: Checkout repository uses: actions/checkout@v5 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::<<AWS Account ID>>:role/WorkshopGitHubActionsRole # <-- PASTE YOUR ROLE ARN HERE aws-region: ${{ env.AWS_REGION }} - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v2 - name: Define image tag id: image-def run: echo "tag=${{ github.sha }}" >> $GITHUB_OUTPUT - name: Build, tag, and push image to Amazon ECR id: build-image env: ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} IMAGE_TAG: ${{ steps.image-def.outputs.tag }} run: | docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f app/Dockerfile ./app docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG - name: Security Scan with Trivy uses: aquasecurity/trivy-action@master with: image-ref: "${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ steps.image-def.outputs.tag }}" format: "table" # Fail the build if Trivy finds any HIGH or CRITICAL severity vulnerabilities exit-code: "1" ignore-unfixed: true vuln-type: "os,library" severity: "CRITICAL,HIGH"
Commit and push to trigger the workflow
Add all your new files to Git, commit them, and push:
git add . git commit -m "feat: Add sample app, Dockerfile, and initial CI workflow" git push origin main
Observe the magic! Go to your GitHub repository, click on the Actions tab. You will see your workflow running. Click on it to see the logs for each step. It will:
- Check out the code.
- Securely connect to AWS.
- Log in to ECR.
- Build and push your Docker image.
- Crucially, it will then run Trivy to scan the image you just pushed.