From Squash Frustration to Full Automation: My Side Project Journey with Python and AWS Lightsail

Introduction

My journey into building a squash court availability checker began out of personal frustration. Repeatedly finding courts fully booked at my preferred club, especially while fumbling with multiple websites on my phone, sparked an idea. This recurring inconvenience prompted me to envision a simple application that could streamline the court booking process for me. Despite juggling the demands of two young children, I committed to dedicating 15-30 minutes whenever possible, gradually transforming a small idea into a tangible project. What started as a quest for a few saved clicks evolved into a comprehensive mini-project: a Python-based FastAPI application hosted on AWS Lightsail, complete with automated deployment via GitHub Actions. In this article, I’ll walk through the development process, the implementation of CI/CD for automated deployment, and the valuable insights gained along the way.

Architecture

With over a decade as an Infrastructure Engineer and five years as a DevOps Engineer, my technical decisions often prioritize practicality over strict software design principles. Given my limited free time, a straightforward architecture was essential. However, I also aimed to incorporate a new learning experience, vowing to utilize an AWS service I hadn’t previously explored. AWS Lightsail emerged as the ideal candidate. This platform provides user-friendly virtual private servers (VPS), containers, storage, and databases, proving perfectly suited for my modest application. Python, a language I’m proficient in, formed the backbone of the backend, powered by FastAPI. For the frontend, I leveraged basic HTML and CSS, much of which was generated with assistance from ChatGPT, compensating for my limited frontend expertise. To effectively parse web pages for court data, I integrated the BeautifulSoup module, renowned for its methods and Pythonic approaches to navigating and manipulating HTML structures. Testing was implemented using pytest and mock, featuring simple assertions for critical functionalities. The entire codebase is managed on GitHub, where GitHub Actions orchestrates an clean and automated build, test, and deployment pipeline to AWS Lightsail with every push to the main branch. The overarching architecture is designed for efficiency and ease of maintenance.

Architecture

Initial App Design

The primary objective was to develop a light, user-friendly web interface for checking squash court availability across Wroclaw. Users would be able to select a date up to a week in advance and choose a full-hour slot between 6:00 and 23:00, aligning with typical facility booking policies. Upon clicking ‘Sprawdz’ (Search), the application would present a list of sports facilities along with their court availability. For the largest club, Hasta La Vista, the app further refined its output by listing individual courts, a valuable feature considering its 32 courts and players’ preferences for specific ones.

How to Pull the Data?!

Once the application’s goals were clearly defined, the next challenge was data acquisition. I began by compiling a list of all squash-offering facilities in Wroclaw. Subsequently, I meticulously investigated each website to extract the necessary information, employing tools like curl and browser developer tools for analysis. After a series of experiments, I crafted my initial Python script, hasta.py. This script utilized requests to fetch website HTML and BeautifulSoup to parse its structure, specifically targeting date and time-based availability. The core logic involved constructing URLs dynamically, sending HTTP GET requests, and then scrutinizing the HTML for availability indicators. The snippet below illustrates this approach:


def check_availability(date_str: str, time_str: str):
    url = f"https://(...)"
    datetime_variants = [
        f"{date_str} {time_str}:00",
        f"{date_str}T{time_str}:00"
    ]
    try:
        resp = requests.get(url, timeout=10)
        resp.raise_for_status()
        data = resp.json()

        if "html" not in data:
            return "❌ Failed to fetch calendar data."

        soup = BeautifulSoup(data["html"], "html.parser")
        all_elements = soup.select("[data-begin]")

        for el in all_elements:
            data_begin = el.get("data-begin")
            if data_begin in datetime_variants:
                text = el.get_text(strip=True)
                if "Book" in text:
                    return (
                        f"✅ Court is available {date_str}  {time_str}"
                        )
                elif "Notify me" in text:
                    return f"❌ Court is not available  ({time_str})  {date_str}"

Within a few evenings, I had developed an early crawler capable of assessing court availability across all local clubs. Concurrently, I started developing a basic frontend to interface with the crawler, again relying heavily on ChatGPT for assistance. This frontend comprised three main files: style.css, index.html, and result.html. With local functionality confirmed, the next logical step was deployment as a container on AWS Lightsail.

Let’s Automate A Few Things

Prior to any manual deployment efforts on AWS, my priority was to automate the entire process. Given that my code resided on GitHub, GitHub Actions was the natural choice for implementing CI/CD. I designed a straightforward pipeline to achieve three key objectives:

  • Build the Docker image
  • Execute tests using pytest
  • Automatically deploy to AWS Lightsail

To enhance security, I opted for OIDC (OpenID Connect) for authentication between GitHub and AWS, thereby eliminating the need for long-lived access keys and aligning with AWS best practices for CI/CD integrations. A simplified representation of the GitHub Actions workflow is provided below:


- name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::${{ env.AWS_ACCOUNT }}:role/github-actions-role
          aws-region: eu-west-1

      - name: Build Docker image
        run: docker build -t court-checker .

      - name: Install lightsailctl
        run: |
          curl "https://s3.us-west-2.amazonaws.com/lightsailctl/latest/linux-amd64/lightsailctl" -o "/usr/local/bin/lightsailctl"
          chmod +x /usr/local/bin/lightsailctl
          /usr/local/bin/lightsailctl --version

      - name: Create Lightsail container service if not exists
        run: |
            set -e
            if ! aws lightsail get-container-services --service-name court-checker >/dev/null 2>&1; then
              echo "Creating Lightsail container service..."
              aws lightsail create-container-service \
                --service-name court-checker \
                --power medium \
                --scale 1
            else
              echo "Lightsail container service already exists."
            fi      

      - name: Push image to Lightsail registry
        id: push
        run: |
          set -euo pipefail
          OUTPUT=$(aws lightsail push-container-image \
            --service-name court-checker \
            --label web \
            --image court-checker:latest)

          echo "$OUTPUT"

          # Extract registryPath from human-readable output
          REGISTRY_PATH=$(echo "$OUTPUT" | grep -oE ':court-checker\.web\.[0-9]+' | head -n 1)

          if [ -z "$REGISTRY_PATH" ]; then
            echo "ERROR: No registryPath found in push output."
            exit 1
          fi
          echo "registry_path=$REGISTRY_PATH" >> $GITHUB_OUTPUT
          echo "Using image: $REGISTRY_PATH"

      - name: Create container config
        run: |
          set -euo pipefail
          cat > container.json < endpoint.json

      - name: Deploy to Lightsail
        run: |
          aws lightsail create-container-service-deployment \
            --service-name court-checker \
            --containers file://container.json \
            --public-endpoint file://endpoint.json

This automated setup ensures that every code change pushed to the main branch is rigorously tested and seamlessly deployed, minimizing manual intervention and maximizing efficiency.

Release And Share With Others

Once thorough testing was complete and I had personally used the application for several days, I decided to soft-launch it by sharing it with a few friends. Their week-long trial provided invaluable feedback, leading to several refinements. Following these improvements, I broadened the app’s reach. While squash isn’t a mainstream sport, I posted about the application in a local Wroclaw Facebook group dedicated to squash enthusiasts, which boasts approximately 3,000 members. This outreach proved successful, as the app now registers between 20 to 50 weekly visits, a gratifying indication that it’s genuinely assisting its users.

Costs

A crucial consideration throughout this project was cost optimization. Fortunately, as an AWS Community Builder, I had access to AWS credits, which afforded me considerable flexibility during the initial testing phases without significant financial concern. Here’s an approximate breakdown of the project’s expenditures:

  • Domain Registration: A one-time payment of around $45 for three years, plus $10 in taxes.
  • AWS Lightsail Container: Operating on the micro tier (0.25 vCPU, 1GB RAM) costs $10 per month. This includes 500 GB/month data transfer, with additional data transfer incurring charges starting from $0.09/GB, depending on the region.
  • GitHub Actions: Benefiting from 2,000 free minutes monthly, my project consumed a mere 103 minutes in August, keeping this aspect well within the free tier.

Thoughts about AWS Lightsail?

AWS Lightsail stands out as an excellent and straightforward platform for deploying small web applications with minimal overhead. It conveniently provides a public DNS by default, formatted as https://<app-name>.<random_digits>.<region>.cs.amazonlightsail.com. Its container services are particularly well-suited for small to medium-sized projects or proof-of-concept developments. While I haven’t utilized its instances, they appear appropriate for more demanding workloads. Coupled with GitHub Actions automation, deployment becomes a worry-free process, executing automatically after every code push. The primary drawbacks I’ve observed include the absence of container-level alarms and metrics (which are only available for instances) and occasional deployment delays. Nevertheless, Lightsail offers a streamlined and cost-effective solution for rapid deployments.

Conclusion And Key Takeaways

Reflecting on this side project, it has far exceeded my initial expectations. What began as a simple endeavor to save time on squash court bookings evolved into a rewarding opportunity for learning, automation, and exploring new AWS services. I wholeheartedly encourage others to embark on similar experimental projects without hesitation. This experience also reinforced the profound impact of consistent effort; dedicating even a small amount of time daily can lead to significant accomplishments. Here are my key takeaways:

  • Embrace Incremental Progress: Consistent, small efforts, even just 15-30 minutes daily, can cumulatively lead to a functional project.
  • AWS Lightsail’s Value: An often-underestimated service, Lightsail excels for small, containerized applications, offering simplicity, predictable costs, and integrated DNS.
  • Automation is Paramount: GitHub Actions streamlines the build, test, and deploy cycles, eliminating manual intervention and AWS Console clicks.
  • Prioritize Security: Implementing OIDC for GitHub-AWS authentication is a robust approach, preventing the need to manage long-lived credentials.
  • Leverage Available Tools: Achieving a solid Minimum Viable Product (MVP) doesn’t require perfection; tools like FastAPI, BeautifulSoup, and even ChatGPT-generated frontend elements can be highly effective.

For those interested in exploring the application, it’s accessible here! While its interface is in Polish, its intuitive design should make it navigable for everyone.

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.
You need to agree with the terms to proceed