softwarerror

CI/CD — GitHub → Docker → AWS Lightsail

This page walks through the real CI/CD pipeline behind this site: code changes are pushed to GitHub, built into Docker images, and deployed to an AWS Lightsail instance using an automated GitHub Actions workflow.

Step 1 — Add a YAML workflow file

Create a workflow under .github/workflows/ in your repository (for example, deploy.yml). This file defines what happens when you push to the main branch.

.github/workflows/deploy.yml

Step 2 — Define when the pipeline runs

The workflow runs on pushes to main, so every commit to the primary branch automatically triggers a deployment.

on:
  push:
    branches: [main]

Step 3 — Define the deploy job and check out the code

The deploy job runs on ubuntu-latest and starts by checking out the repository so the workflow can build the Docker image from the latest code.

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

Step 4 — Set up Docker Buildx and log in to Docker Hub

The next steps prepare the build environment using docker/setup-buildx-action and authenticate to Docker Hub using credentials stored as GitHub Secrets.

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

Using secrets keeps credentials out of the repository while still allowing the workflow to push images to your Docker Hub account.

Step 5 — Build and push the Docker image

The workflow then builds a Linux/amd64 Docker image for the app and pushes it to Docker Hub under mmfay3/softwarerror:latest.

      - name: Build and Push Docker Image (linux/amd64)
        run: |
          docker buildx build \
            --platform linux/amd64 \
            -t mmfay3/softwarerror:latest \
            --push .

Using buildx makes it easy to target the same architecture as your Lightsail instance and push in a single step.

Step 6 — SSH into AWS Lightsail and deploy the container

Finally, the workflow uses appleboy/ssh-action to SSH into the Lightsail instance, pull the latest image, stop/remove the old container, and run the new one with the correct environment variables.

      - name: SSH into Lightsail and deploy
        uses: appleboy/ssh-action@v1.0.0
        with:
          host: ${{ secrets.HOST }}
          username: ubuntu
          key: ${{ secrets.HOST_SSH_KEY }}
          script: |
            docker pull mmfay3/softwarerror:latest
            docker stop softwarerror || true
            docker rm softwarerror || true
            docker run -d --restart always --name softwarerror \
              -p 3000:3000 \
              -e EMAIL_HOST=${{ secrets.EMAIL_HOST }} \
              -e EMAIL_PORT=${{ secrets.EMAIL_PORT }} \
              -e EMAIL_USER=${{ secrets.EMAIL_USER }} \
              -e EMAIL_PASS=${{ secrets.EMAIL_PASS }} \
              mmfay3/softwarerror:latest

This keeps deployment repeatable: every push to main results in the Lightsail instance running the latest version of the app with the same port mapping and environment settings.

Step 7 — Health checks & next steps

Right now, verification is manual (hitting the site and confirming it's responding). This could be extended with an automated health check step in the workflow or external monitoring/alerting.

  • Add a simple curl-based health check after deployment.
  • Hook uptime monitoring to the public URL.
  • Extend logging/metrics for deeper visibility.