Skip to content

Commit

Permalink
added support via docker
Browse files Browse the repository at this point in the history
  • Loading branch information
ahmedk2 committed Dec 30, 2024
1 parent 8b6ccaf commit d459115
Show file tree
Hide file tree
Showing 14 changed files with 166 additions and 3 deletions.
39 changes: 39 additions & 0 deletions .github/workflows/github-action-docker-image-push.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Publish Docker image

on:
push:
branches:
- main

jobs:
push_to_registry:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
name: Check out code

- uses: mr-smithers-excellent/docker-build-push@v6
name: Build & push Docker image frontnend
with:
image: kahmed23/serverlessmovies
tags: frontend-v1, latest
registry: docker.io
directory: frontend
dockerfile: Dockerfile
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- uses: mr-smithers-excellent/docker-build-push@v6
name: Build & push Docker image backend
with:
image: kahmed23/serverlessmovies
tags: backend-v1, latest
registry: docker.io
directory: backend
dockerfile: Dockerfile
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}


2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.env
.vscode
20 changes: 20 additions & 0 deletions Notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,23 @@ Instead, your backend must return the applicable CORS headers, such as Access-Co
```

I was using lambda proxy integration for this specific API resource `/getmoviesbyyear` so the above made sense but I did not properly take this in until an hour later.

## Phase 4

My lambda function was no longer helpful. I needed to setup a backend that communicates with front end to retrieve movie data. In phase 3, I used only a front end and api gateway URLs to fetch DyanmoDB data via lambda functions.

I tried making a backend in flask, got it working doing some terminal commands and installing packages (`specify later check terminal history`). I had issues with cors and other flask related errors. Resolved it by installing a specific version of flask cors package (`pip install flask-cors==3.0.7`) that supports python 2.7. Since newer package version is meant for python 3. There were some JSON serialization errors but I used the same solution as when I was making the lambda function.

I got it all working locally by editing the front end to reference backend endpoint/ports

For the backend to function locally you need to setup your environment variables to your AWS key tokens. You can change the name of the dynamoDB table to be your table. Then you can do the following:

`docker compose up`

After I got the container running I had errors with the backend not being reachable outside the container. There were no errors in the logs and the port was open. However after researching I noticed flask does app binding and network configs inside the container and not available outside it by default.

So I needed to modify the binding address to include host 0.0.0.0 making it publicly available.

The address 0.0.0.0 tells Flask to listen on all network interfaces inside the container, making the app accessible from the Docker host and any other network interfaces mapped by Docker.

Without 0.0.0.0, the app inside the container would not “see” requests coming from outside, even though Docker’s networking is correctly set up.
2 changes: 2 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
AWS_ACCESS_KEY_ID=value_here
AWS_SECRET_ACCESS_KEY=value_here
10 changes: 10 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM python:3.12
WORKDIR /usr/local/app

# Install the application dependencies
COPY . ./
RUN pip install --no-cache-dir -r requirements.txt

EXPOSE 5000

CMD ["python", "flask-db-request.py", "--port", "5000"]
61 changes: 61 additions & 0 deletions backend/flask-db-request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from flask import Flask, jsonify, request
from flask_cors import CORS
import boto3
from boto3.dynamodb.conditions import Key
import os
from decimal import Decimal

# Initialize Flask app
app = Flask(__name__)

# Enable CORS for all routes
CORS(app)

# Set up DynamoDB session
session = boto3.Session(
region_name='us-east-1'
)
dynamodb = session.resource(
'dynamodb',
aws_access_key_id=os.getenv('AWS_ACCESS_KEY_ID'),
aws_secret_access_key=os.getenv('AWS_SECRET_ACCESS_KEY')
)

# Connect to the DynamoDB table
table = dynamodb.Table('serverlessMovieApiTable')

# Helper function to handle Decimal type from DynamoDB
def decimal_to_float(data):
if isinstance(data, list):
return [decimal_to_float(item) for item in data]
elif isinstance(data, dict):
return {key: decimal_to_float(value) for key, value in data.items()}
elif isinstance(data, Decimal):
return float(data)
return data

# Route to get all movies
@app.route('/movies', methods=['GET'])
def get_all_movies():
try:
response = table.scan()
items = decimal_to_float(response['Items']) # Convert Decimals to floats
return jsonify(items), 200
except Exception as e:
return jsonify({'error': str(e)}), 500

# Route to get movies by year
@app.route('/movies/<int:year>', methods=['GET'])
def get_movies_by_year(year):
try:
response = table.query(
KeyConditionExpression=Key('releaseYear').eq(year)
)
items = decimal_to_float(response['Items']) # Convert Decimals to floats
return jsonify(items), 200
except Exception as e:
return jsonify({'error': str(e)}), 500

# Run the Flask app
if __name__ == '__main__':
app.run(debug=True, host="0.0.0.0", port=5000)
6 changes: 6 additions & 0 deletions backend/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Flask==1.1.4
Flask-Cors==3.0.10
boto3==1.28.0 # Or the latest compatible version
botocore==1.31.0 # Ensure compatibility with boto3
python-dotenv==0.18.0
markupsafe<2.1
12 changes: 12 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
services:
backend-api:
build: ./backend
env_file:
- ./backend/.env
ports:
- "5000:5000"

frontend-web:
build: ./frontend
ports:
- "80:80"
11 changes: 11 additions & 0 deletions frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM nginx:stable

WORKDIR /usr/share/nginx/html

# Copy the HTML, CSS, and JavaScript files into the container
COPY . ./

EXPOSE 80

# Start Nginx when the container starts
CMD ["nginx", "-g", "daemon off;"]
6 changes: 3 additions & 3 deletions index.html → frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,10 @@ <h1>Movie Explorer</h1>
<script>
async function fetchMovies() {
try {
const response = await fetch('https://1sxwpeubpk.execute-api.us-east-1.amazonaws.com/prod/');
const response = await fetch('http://127.0.0.1:5000/movies');
if (!response.ok) throw new Error(`Error: ${response.status}`);
const data = await response.json();
displayMovies(data.body);
displayMovies(data);
} catch (error) {
console.error('Failed to fetch all movies:', error);
document.getElementById('moviesContainer').innerHTML = '<p>Failed to load movies. Please try again later.</p>';
Expand All @@ -116,7 +116,7 @@ <h1>Movie Explorer</h1>
return;
}
try {
const response = await fetch(`https://1sxwpeubpk.execute-api.us-east-1.amazonaws.com/prod/getmoviesbyyear?releaseyear=${year}`);
const response = await fetch(`http://127.0.0.1:5000/movies/${year}`);
if (!response.ok) throw new Error(`Error: ${response.status}`);
const data = await response.json();
console.log(data)
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 comments on commit d459115

Please sign in to comment.