DEV Community

From Frustration to Automation: Building a Squash Court Availability App

Introduction

A few months back, I wanted to reserve a squash court, as usual, on a Wednesday evening. Unfortunately, everything was booked in my favorite club, so I started looking for other places. I was on my phone, which wasn’t very convenient, and every website was different — so I quickly got frustrated.

This situation happened a few times, and then I realized I could make this task easier by creating a small app to check court availability for me. It seemed like a good small project. I have two small kids, so I don’t have much free time, but I decided to spend 15–30 minutes on it whenever I could — and here we are!

What started as a quick idea to save a few clicks, turned into a mini project — a Python + FastAPI app hosted on AWS Lightsail, with full GitHub Actions automation.
In this post, I will share how I built it, automated deployment with CI/CD, and what I learned along the way.

Architecture

During my IT career, I spent over 10 years as an Infrastructure Engineer and the next 5 as a DevOps Engineer, so I’m not really a programmer. That’s why my technical choices were mostly about simplicity rather than software design perfection.

As I mentioned, time wasn’t on my side, so I decided on a simple architecture. Still, I wanted to learn something new, so I promised myself to use at least one AWS service I hadn’t worked with before.
The choice fell on AWS Lightsail.

Amazon Lightsail offers easy-to-use virtual private servers (VPS), containers, storage, and databases. It’s really intuitive and fits perfectly for my small app.

I’m quite comfortable with Python, so the backend is built with FastAPI. For the frontend, I used some simple HTML and CSS - generated mostly by ChatGPT, since I don’t have strong frontend experience.

To parse the web pages, I used the BeautifulSoup module, which provides methods and Pythonic idioms to navigate, search, and modify the parsed HTML tree.

Tests are based on pytest and mock, with simple assertions for specific use cases.

The code is hosted on GitHub, and I use GitHub Actions to build, test, and deploy automatically to AWS Lightsail after every push to the main branch.

Here’s how the overall architecture looks:

Architecture

Initial app design

The main goal was to create a lightweight, intuitive web page to check squash court availability in Wroclaw.
The user can choose a date from the calendar (up to one week ahead) and select an hour between 06:00 and 23:00 (full hours only, since most facilities don’t support half-hour bookings).

After selecting the date and hour, the user clicks “Sprawdz” (means Search), and all sports facilities are listed with availability information. For the biggest club, Hasta La Vista, the app also lists individual courts — since there are 32 of them and players often prefer specific ones.

How to pull the data?!

Once I knew what I wanted to achieve, I started figuring out how to do it.
First, I gathered all sports facilities in Wroclaw that offer squash. Then, one by one, I tried to fetch the data I needed. Tools like curl and browser dev tools helped a lot.

After a few experiments, I wrote my first Python file — hasta.py. Using requests and BeautifulSoup, I fetched each website’s HTML and parsed the structure based on date and time. It looked like follow:

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}"

Enter fullscreen mode Exit fullscreen mode

After a few evenings, I had an early version of a crawler that could check court availability for all clubs in the city.
In the meantime, I also started working on a simple frontend to connect the crawler with a web interface — again, as I have already mentioned, mostly with ChatGPT’s help.

The frontend consists of three main files: style.css, index.html, and result.html.
Everything was working well locally, so I decided to deploy it as a container on AWS Lightsail.

Let's Automate A Few Things

Before deploying anything manually to AWS, I wanted to automate the process as much as possible.
Since the code was already on GitHub, GitHub Actions was a natural choice for CI/CD. I decided to build a simple pipeline that would:

  • Build the Docker image

  • Run tests using pytest

  • Deploy automatically to AWS Lightsail

I also decided to use OIDC (OpenID Connect) for authentication between GitHub and AWS, so I didn’t need to store long-lived access keys (you can check on OIDC Stack in my previous blog post regarding AWS Serverless). This approach is more secure and follows AWS best practices for CI/CD integration.

Here’s a simplified version of the GitHub Actions workflow:

- 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 <<EOF
          {
            "web": {
              "image": "${{ steps.push.outputs.registry_path }}",
              "ports": {
                "8000": "HTTP"
              }
            }
          }
          EOF
          cat container.json

      - name: Create endpoint config
        run: |
          echo '{
            "containerName": "web",
            "containerPort": 8000,
            "healthCheck": {
              "path": "/health",
              "successCodes": "200-499",
              "timeoutSeconds": 5,
              "intervalSeconds": 10,
              "healthyThreshold": 2,
              "unhealthyThreshold": 2
            }
          }' > 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
Enter fullscreen mode Exit fullscreen mode

Release And Share With Others

Once all tests were done and I had used the app myself for a couple of days, I decided to share it with a few of my friends.
They tried it for about a week, gave me some feedback, I fixed a few things, and then decided to share it with a wider group.

Squash isn’t super popular, but I posted about the app in a local Facebook group with around 3,000 squash enthusiasts from Wroclaw so they could give it a try.
Now I can see there are around 20–50 visits per week — which makes me really happy that at least some people are finding it useful!

Costs

Obviously, I wanted to keep costs as low as possible. Fortunately, I’m an AWS Community Builder, so I have some AWS credits, which gave me flexibility in initial testing and I did not need to bother too much about that.
Here’s the rough cost breakdown:

  • Domain registration: around $45 for three years + $10 tax (one-time cost)

  • AWS Lightsail container (micro tier: 0.25 vCPU, 1GB RAM): $10/month
    Each container service includes 500 GB/month data transfer. Extra data costs start at $0.09/GB depending on the region.

  • GitHub Actions: 2,000 free minutes per month (my project used only 103 minutes in August)

Thoughts about AWS Lightsail?

It’s definitely a great and easy way to deploy small web apps with minimal effort.
You get a public DNS by default:
https://<app-name>.<random_digits>.<region>.cs.amazonlightsail.com

Containers are perfect for small/medium projects or proof-of-concepts. I didn’t use instances, but they seem suitable for larger workloads.

With GitHub Actions automation, I don’t need to worry about deployment — it happens automatically after every push.
The only downsides I’ve noticed are:

  • Lack of container-level alarms and metrics (available only for instances)

  • Occasional delays during deployment

Overall, it’s a neat and simple setup for quick and cost-effective deployments.

Conclusion And Key Takeaways

Looking back, this small side project turned out to be much more than I expected.
What started as a simple idea to save time booking a squash court became a fun way to learn, automate, and explore new AWS services. I truly recommend that everyone try something like this and not be afraid to experiment.
I also realized how much can be achieved by dedicating just a little time each day — consistency really does beat intensity.
Here are also a few takeaways I’d like to share as a summary:

  • Start small, iterate often: Even with just 15–30 minutes a day, you can deliver a working project over time.

  • AWS Lightsail is underrated: It’s perfect for small, containerized apps — simple setup, predictable cost, and built-in DNS.

  • Automation saves time: GitHub Actions makes it easy to build, test, and deploy without manual steps or AWS Console clicks.

  • Security by design: Using OIDC between GitHub and AWS avoids storing long-lived credentials.

  • Don’t fear imperfect tools: FastAPI, BeautifulSoup, and a bit of ChatGPT-generated frontend were enough to get a solid MVP online.

And if you’d like to take a look at the app yourself, check it out here! It is in Polish however should be intuitive for everyone.

Top comments (0)