AI Trade Platform with DevOps Pipeline

Build a containerized AI trade app and deploy it to AWS EC2 with CI/CD.

Introduction

30 Second Summary

International trade between countries involves complex decisions about what to ship, how to comply with regulations, and how to keep costs down. Businesses spend hours researching before making a single trade decision.

In this project, you will build an AI-powered trade recommendation platform that helps importers and exporters between Bangladesh and Germany find optimal trade paths. You'll containerize the app with Docker, automate deployment with a CI/CD pipeline, and deploy it to the cloud.

What You'll Build

A full-stack web application called TradeConnect BD-DE that uses Google Gemini AI to analyze trade queries and return structured recommendations including product categories, compliance notes, and cost optimization suggestions.

By the end of this project, you'll have:

  • A FastAPI backend integrated with Google Gemini AI that generates intelligent trade recommendations from natural language queries.
  • A containerized application using Docker and Docker Compose that runs identically on any machine.
  • A CI/CD pipeline on GitHub Actions that automatically builds, tests, and publishes Docker images on every commit.
  • A live deployment on AWS EC2 accessible via a public URL.
  • Secret Mission: Add a route comparison endpoint that compares shipping routes and suggests the most cost-effective logistics path.

Are there any prerequisites?

You should have basic web development experience and familiarity with REST APIs. No prior Docker or CI/CD experience is needed.

Set Up Your Development Environment

You have Python, Docker, and your Google Gemini API key ready to go. Before writing application code, you need a structured project folder with pinned dependencies and a working Python environment.

In this step, you'll create the project directory, install the two packages TradeConnect BD-DE depends on, and configure your API key for local development.

In this step, get ready to:
  • Create the project folder and open it in Cursor.
  • Install FastAPI and the Google GenAI SDK in a virtual environment.
  • Set the Gemini API key as an environment variable.
Create the project folder

Every project needs a home. You'll create a dedicated folder on your Desktop so it's easy to find later.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create the project folder and move into it by running these commands:
mkdir tradeconnect-bd-de
cd tradeconnect-bd-de
  • Confirm the folder exists by running:
ls

The folder is empty for now, so you'll see no output. That's expected.

Now open the project folder in Cursor so you can work inside it for the rest of the project.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Cursor and press Enter to open it.
  • Click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop, select the tradeconnect-bd-de folder, and click Open (macOS) or Select Folder (Windows).

What should I see?

Cursor reloads with the tradeconnect-bd-de folder open in the Explorer sidebar. The folder is empty since you haven't added any files yet.

Pin your dependencies

A requirements.txt file tells Python exactly which packages (and versions) your project needs. This keeps your environment reproducible across machines.

  • In Cursor's Explorer sidebar, hover over the folder name and click the New File icon (a page with a plus sign).
  • Name the file requirements.txt and press Enter.
  • Add the following dependency pins to requirements.txt:
fastapi[standard]==0.136.3
google-genai==2.8.0

What do these packages do?

  • fastapi[standard]==0.136.3 installs FastAPI along with its recommended extras (the development server, Pydantic for data validation, and more).
  • google-genai==2.8.0 is the official Google GenAI SDK. It connects your app to Gemini 2.5 Flash for AI-powered trade recommendations.
  • Save requirements.txt with Cmd+S (macOS) or Ctrl+S (Windows).

You should see requirements.txt listed in Cursor's Explorer sidebar.

✔️ Awesome, I've got everything!

Great. Double check you've saved the file before moving on.

ⓧ I'd like to double check the full code

fastapi[standard]==0.136.3
google-genai==2.8.0
Install packages and set your API key

A virtual environment isolates your project's packages from the rest of your system. This prevents version conflicts between projects.

  • Open Cursor's integrated terminal by pressing Ctrl+` (Windows) or Cmd+` (macOS).

The terminal opens inside the tradeconnect-bd-de folder automatically since that's what Cursor has open.

  • Create and activate a virtual environment by running these commands:

macOS

python3 -m venv venv
source venv/bin/activate

Windows

python -m venv venv
venv\Scripts\activate

What should I see?

After activation, your terminal prompt changes to show (venv) at the beginning. This confirms you're working inside the virtual environment.

  • Install the project dependencies by running:
pip install -r requirements.txt

What does this command do?

pip reads requirements.txt and downloads the exact versions you pinned. The -r flag tells pip to install from a file rather than a single package name.

You should see a stream of download progress ending with Successfully installed followed by the package names.

Seeing an error during install?

  • Make sure your virtual environment is active. You should see (venv) in your terminal prompt.
  • Check that requirements.txt is saved and matches the code above exactly. A typo in the package name or version will cause a failure.
  • Confirm your Python version is 3.10 or higher. The google-genai package requires Python 3.10+.

Why can't I install my Python packages?

Finally, your app needs access to the Gemini API key you generated earlier. You'll set it as an environment variable so the code can read it at runtime.

  • Set your API key as an environment variable by running this command (replace your-api-key-here with the key from Google AI Studio):

macOS

export GEMINI_API_KEY="your-api-key-here"

Windows

$env:GEMINI_API_KEY = "your-api-key-here"

Why an environment variable?

Environment variables keep sensitive values out of your source code. Your app reads the key from the system at runtime instead of having it hardcoded in a file.

This variable is session-specific. If you close and reopen the terminal, you'll need to run the export command again.

  • Verify the variable is set by running:

macOS

echo $GEMINI_API_KEY

Windows

echo $env:GEMINI_API_KEY

You should see your API key printed back in the terminal.

Seeing blank output?

  • Make sure you ran the export command in the same terminal session. Opening a new terminal tab resets environment variables.
  • Check for typos in the variable name. It must be exactly GEMINI_API_KEY.

Help me set my GEMINI_API_KEY environment variable.

Your environment is ready. Next up, you'll build the FastAPI backend that powers trade recommendations with Gemini AI.

Build the FastAPI Backend with Gemini AI

Your development environment is ready with Python, the Gemini API key, and all dependencies installed. Now it is time to write the actual application code.

In this step, you will build a FastAPI backend that connects to Google's Gemini AI model. When a user describes a trade they want to make between Bangladesh and Germany, the API sends their query to Gemini with a specialized system prompt and returns structured recommendations.

In this step, get ready to:
  • Create the FastAPI app structure with the Gemini AI client.
  • Write a trade-optimized system prompt for accurate recommendations.
  • Build the /recommend endpoint to serve AI-powered trade advice.
Set up the FastAPI app and Gemini client

Every FastAPI application starts with an app instance and its dependencies. Your app needs to import the web framework, Pydantic for data validation, and the Google GenAI SDK to communicate with Gemini.

  • Create a new file called main.py in your project's root folder by right-clicking in the Cursor **Explorer** sidebar and selecting **New File**.
  • Also create a folder called static in the same location by right-clicking and selecting **New Folder**. You will use this folder for the frontend in the next step.
  • Open main.py and paste the following code to set up the app foundation:
import os
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from pydantic import BaseModel
from google import genai
from google.genai import types

# Create the FastAPI application instance
app = FastAPI(title="TradeConnect BD-DE")

# Read the API key from environment and create the Gemini client
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "your-api-key-here")
client = genai.Client(api_key=GEMINI_API_KEY)

What does this code do?

The imports bring in FastAPI for the web server, Pydantic for request/response validation, and the google-genai SDK for communicating with Gemini.

The os.environ.get call reads your GEMINI_API_KEY from the environment variable you set in Step 1. The genai.Client creates a reusable connection to Google's AI service.

  • Save main.py.
  • You will not see any visible output yet. The next chunks add the prompt, models, and endpoint that make the app functional.

Seeing a red underline on imports?

If Cursor shows import errors, make sure your Python interpreter is set to the virtual environment you created in Step 1. Press Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows), type Python: Select Interpreter, and choose the one inside your project's venv folder.

help me fix import errors for google-genai in my FastAPI project

Write the trade system prompt and data models

The system prompt is the key to getting useful AI responses. It gives Gemini the context of Bangladesh-Germany trade patterns so the model can provide specific, actionable recommendations instead of generic answers.

  • In main.py, add the following system prompt below the client line:
TRADE_SYSTEM_PROMPT = """You are an expert trade advisor specializing in Bangladesh-Germany bilateral commerce.
Bangladesh-Germany bilateral trade was USD 9.81 billion in 2024.
Germany is Bangladesh's second-largest export destination (10.96% of total exports).
Key trade flows:
- Bangladesh to Germany: textiles (90%+ of exports), pharmaceuticals, leather goods, footwear
- Germany to Bangladesh: machinery ($241M), medical/optical apparatus ($68M), electronics ($62M), chemicals ($46M), food products ($16M)

When a user describes a trade they want to make, provide:
1. Recommended product categories with estimated HS code ranges
2. Key compliance and regulatory considerations (EU/Bangladesh regulations)
3. Cost optimization suggestions
4. Similar successful trade patterns

Format your response as structured JSON with keys: product_categories, compliance_notes, cost_optimization, trade_patterns."""

Why include trade data in the prompt?

By feeding Gemini real bilateral trade statistics and product categories, the model grounds its recommendations in actual trade patterns. Without this context, it would give generic international trade advice that is not specific to the Bangladesh-Germany corridor.

The structured JSON output format at the end ensures the response is machine-readable, which makes it easier to display cleanly in the frontend later.

  • Save main.py.
  • You will not see a change yet. The next chunk defines the data models that shape requests and responses.

Now define the Pydantic models that describe what data the API accepts and returns. These models enforce validation automatically, so malformed requests get rejected before hitting the AI.

  • In main.py, add these two classes below the TRADE_SYSTEM_PROMPT variable:
# Define the shape of incoming trade queries
class TradeQuery(BaseModel):
    query: str
    direction: str = "bd_to_de"

# Define the shape of outgoing recommendations
class TradeRecommendation(BaseModel):
    query: str
    direction: str
    recommendation: str

What do these models do?

TradeQuery defines what the frontend sends: a free-text query describing the trade, and a direction field that defaults to Bangladesh-to-Germany.

TradeRecommendation defines what the API returns: the original query, the direction, and the AI-generated recommendation text. FastAPI uses these models to auto-generate API documentation and validate data in both directions.

  • Save main.py.
Build the /recommend and /health endpoints

The /recommend endpoint is the core of your application. It takes a trade query, sends it to Gemini with your system prompt, and returns the AI's structured recommendation.

  • In main.py, add the /recommend endpoint below the TradeRecommendation class:
@app.post("/recommend", response_model=TradeRecommendation)
async def get_recommendation(trade_query: TradeQuery):
    # Determine human-readable direction label
    direction_label = (
        "Bangladesh to Germany"
        if trade_query.direction == "bd_to_de"
        else "Germany to Bangladesh"
    )
    # Build the prompt that gets sent to Gemini
    user_prompt = f"Trade direction: {direction_label}\nUser query: {trade_query.query}"

    # Call Gemini 2.5 Flash with the trade system prompt
    response = client.models.generate_content(
        model="gemini-2.5-flash",
        contents=user_prompt,
        config=types.GenerateContentConfig(
            system_instruction=TRADE_SYSTEM_PROMPT,
            temperature=0.3,
            max_output_tokens=1024,
        ),
    )

    # Return the structured response
    return TradeRecommendation(
        query=trade_query.query,
        direction=trade_query.direction,
        recommendation=response.text,
    )

What does this endpoint do?

The @app.post("/recommend") decorator registers a POST endpoint. FastAPI automatically parses the request body into a TradeQuery object.

The client.models.generate_content call sends the user's query plus the system prompt to Gemini 2.5 Flash. The low temperature=0.3 keeps responses factual rather than creative.

  • Save main.py.
  • You will not see output yet. The next chunk adds a health check so you can verify the server starts correctly.

A health check endpoint gives you a quick way to confirm the server is running without calling the AI.

  • Add the /health endpoint at the bottom of main.py:
@app.get("/health")
async def health_check():
    return {"status": "healthy", "service": "TradeConnect BD-DE"}

Why add a health check?

Health endpoints are standard in production APIs. Your CI/CD pipeline and cloud deployment will use this endpoint later to verify the container is running correctly without consuming AI tokens.

  • Save main.py.
  • Open the terminal in Cursor with Ctrl+` and run the health check test by running this command:
python -c "from fastapi.testclient import TestClient; from main import app; r = TestClient(app).get('/health'); print(r.json())"
  • You should see {"status": "healthy", "service": "TradeConnect BD-DE"} printed in the terminal.

Seeing a ModuleNotFoundError?

Make sure your virtual environment is activated. If you see an error about fastapi or google not being found, activate your virtual environment first and try again.

help me fix ModuleNotFoundError when importing fastapi or google-genai

✔️ Awesome, I've got everything!

Great. Double check you have saved main.py and that the static folder exists in your project root.

ⓧ I'd like to double check the full code

Here is the complete main.py at the end of this step. Compare it against yours and fix any differences.

import os
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from pydantic import BaseModel
from google import genai
from google.genai import types

app = FastAPI(title="TradeConnect BD-DE")

GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "your-api-key-here")
client = genai.Client(api_key=GEMINI_API_KEY)

TRADE_SYSTEM_PROMPT = """You are an expert trade advisor specializing in Bangladesh-Germany bilateral commerce.
Bangladesh-Germany bilateral trade was USD 9.81 billion in 2024.
Germany is Bangladesh's second-largest export destination (10.96% of total exports).
Key trade flows:
- Bangladesh to Germany: textiles (90%+ of exports), pharmaceuticals, leather goods, footwear
- Germany to Bangladesh: machinery ($241M), medical/optical apparatus ($68M), electronics ($62M), chemicals ($46M), food products ($16M)

When a user describes a trade they want to make, provide:
1. Recommended product categories with estimated HS code ranges
2. Key compliance and regulatory considerations (EU/Bangladesh regulations)
3. Cost optimization suggestions
4. Similar successful trade patterns

Format your response as structured JSON with keys: product_categories, compliance_notes, cost_optimization, trade_patterns."""


class TradeQuery(BaseModel):
    query: str
    direction: str = "bd_to_de"


class TradeRecommendation(BaseModel):
    query: str
    direction: str
    recommendation: str


@app.post("/recommend", response_model=TradeRecommendation)
async def get_recommendation(trade_query: TradeQuery):
    direction_label = (
        "Bangladesh to Germany"
        if trade_query.direction == "bd_to_de"
        else "Germany to Bangladesh"
    )
    user_prompt = f"Trade direction: {direction_label}\nUser query: {trade_query.query}"

    response = client.models.generate_content(
        model="gemini-2.5-flash",
        contents=user_prompt,
        config=types.GenerateContentConfig(
            system_instruction=TRADE_SYSTEM_PROMPT,
            temperature=0.3,
            max_output_tokens=1024,
        ),
    )

    return TradeRecommendation(
        query=trade_query.query,
        direction=trade_query.direction,
        recommendation=response.text,
    )


@app.get("/health")
async def health_check():
    return {"status": "healthy", "service": "TradeConnect BD-DE"}

Your backend is ready. Next up, you will build a simple HTML frontend that lets users type trade queries and see the AI recommendations in their browser.

Create the Frontend and Test Locally

Your backend API is ready to accept trade queries and return AI-powered recommendations. Now you need a way for users to actually interact with it.

In this step, you'll build a simple web frontend with an HTML form, wire it up to your FastAPI backend using JavaScript, and test the entire flow locally.

In this step, get ready to:
  • Build an HTML/CSS frontend with a trade query form.
  • Connect the frontend to the backend using JavaScript fetch.
  • Run the app locally and verify the full AI recommendation flow.
Build the HTML/CSS frontend with a trade query form

Users need a form where they can select a trade direction (Bangladesh to Germany or vice versa) and describe what they want to trade. You'll create an index.html file inside your static/ directory with all the structure and styling.

  • Create a new file called index.html inside the static/ folder in your tradeconnect-bd-de project.
  • Paste the following code into static/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TradeConnect BD-DE</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: system-ui, sans-serif; background: #f0f4f8; min-height: 100vh; padding: 2rem; }
        .container { max-width: 800px; margin: 0 auto; }
        h1 { color: #1a365d; margin-bottom: 0.5rem; }
        .subtitle { color: #4a5568; margin-bottom: 2rem; }
        .card { background: white; border-radius: 12px; padding: 2rem; box-shadow: 0 4px 6px rgba(0,0,0,0.07); margin-bottom: 1.5rem; }
        label { display: block; font-weight: 600; margin-bottom: 0.5rem; color: #2d3748; }
        select, textarea { width: 100%; padding: 0.75rem; border: 1px solid #e2e8f0; border-radius: 8px; font-size: 1rem; margin-bottom: 1rem; }
        textarea { min-height: 100px; resize: vertical; }
        button { background: #2b6cb0; color: white; border: none; padding: 0.75rem 1.5rem; border-radius: 8px; font-size: 1rem; cursor: pointer; }
        button:hover { background: #2c5282; }
        button:disabled { background: #a0aec0; cursor: not-allowed; }
        .result { background: #f7fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 1.5rem; white-space: pre-wrap; font-size: 0.9rem; line-height: 1.6; }
        .loading { display: none; color: #4a5568; padding: 1rem; }
    </style>
</head>
<body>
    <div class="container">
        <h1>TradeConnect BD-DE</h1>
        <p class="subtitle">AI-powered trade recommendations for Bangladesh-Germany commerce</p>

        <div class="card">
            <label for="direction">Trade Direction</label>
            <select id="direction">
                <option value="bd_to_de">Bangladesh to Germany</option>
                <option value="de_to_bd">Germany to Bangladesh</option>
            </select>

            <label for="query">Describe your trade</label>
            <textarea id="query" placeholder="e.g., I want to export medical instruments from Germany to Bangladesh"></textarea>

            <button id="submitBtn" onclick="getRecommendation()">Get AI Recommendation</button>
        </div>

        <div class="loading" id="loading">Analyzing your trade query with AI...</div>

        <div class="card" id="resultCard" style="display:none;">
            <label>AI Recommendation</label>
            <div class="result" id="result"></div>
        </div>
    </div>
</body>
</html>

What does this code do?

The <style> block sets up a clean card-based layout with a blue color scheme and responsive sizing.

  • The <select> dropdown lets users pick the trade direction: Bangladesh to Germany or Germany to Bangladesh.
  • The <textarea> gives users space to describe their trade query in natural language.
  • The submitBtn button triggers a getRecommendation() function (which you'll add next) when clicked.
  • The resultCard div is hidden by default and will display the AI recommendation once it arrives.
  • Save static/index.html.
  • Confirm the file exists by running:
ls static/

You should see index.html listed in the output.

Don't see index.html?

  • Make sure you created the file inside the static/ folder, not in the project root.
  • Check that the folder name is exactly static (lowercase, no typos).

Help me figure out why my index.html file isn't showing up in the static/ directory.

Connect the frontend to the backend with JavaScript fetch

Your HTML form looks nice, but clicking the button does nothing yet. You need JavaScript to grab the user's input, send it to your /recommend endpoint, and display the response.

  • In static/index.html, find the closing </div> tag just above </body>.
  • Add the following <script> block between that closing </div> and </body>:
    <script>
        async function getRecommendation() {
            const query = document.getElementById('query').value;
            const direction = document.getElementById('direction').value;
            const btn = document.getElementById('submitBtn');
            const loading = document.getElementById('loading');
            const resultCard = document.getElementById('resultCard');
            const result = document.getElementById('result');

            if (!query.trim()) return;

            btn.disabled = true;
            loading.style.display = 'block';
            resultCard.style.display = 'none';

            try {
                const response = await fetch('/recommend', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ query, direction })
                });
                const data = await response.json();
                result.textContent = data.recommendation;
                resultCard.style.display = 'block';
            } catch (error) {
                result.textContent = 'Error: ' + error.message;
                resultCard.style.display = 'block';
            } finally {
                btn.disabled = false;
                loading.style.display = 'none';
            }
        }
    </script>

What does this script do?

  • It reads the user's trade description and direction from the form inputs.
  • It disables the button and shows a loading message while the request is in progress.
  • It sends a POST request to /recommend with query and direction as JSON, matching your TradeQuery Pydantic model.
  • On success, it displays the AI recommendation. On failure, it shows the error message.
  • Save static/index.html.

You won't see a change yet because the server isn't running. The next substep wires everything together.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TradeConnect BD-DE</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: system-ui, sans-serif; background: #f0f4f8; min-height: 100vh; padding: 2rem; }
        .container { max-width: 800px; margin: 0 auto; }
        h1 { color: #1a365d; margin-bottom: 0.5rem; }
        .subtitle { color: #4a5568; margin-bottom: 2rem; }
        .card { background: white; border-radius: 12px; padding: 2rem; box-shadow: 0 4px 6px rgba(0,0,0,0.07); margin-bottom: 1.5rem; }
        label { display: block; font-weight: 600; margin-bottom: 0.5rem; color: #2d3748; }
        select, textarea { width: 100%; padding: 0.75rem; border: 1px solid #e2e8f0; border-radius: 8px; font-size: 1rem; margin-bottom: 1rem; }
        textarea { min-height: 100px; resize: vertical; }
        button { background: #2b6cb0; color: white; border: none; padding: 0.75rem 1.5rem; border-radius: 8px; font-size: 1rem; cursor: pointer; }
        button:hover { background: #2c5282; }
        button:disabled { background: #a0aec0; cursor: not-allowed; }
        .result { background: #f7fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 1.5rem; white-space: pre-wrap; font-size: 0.9rem; line-height: 1.6; }
        .loading { display: none; color: #4a5568; padding: 1rem; }
    </style>
</head>
<body>
    <div class="container">
        <h1>TradeConnect BD-DE</h1>
        <p class="subtitle">AI-powered trade recommendations for Bangladesh-Germany commerce</p>

        <div class="card">
            <label for="direction">Trade Direction</label>
            <select id="direction">
                <option value="bd_to_de">Bangladesh to Germany</option>
                <option value="de_to_bd">Germany to Bangladesh</option>
            </select>

            <label for="query">Describe your trade</label>
            <textarea id="query" placeholder="e.g., I want to export medical instruments from Germany to Bangladesh"></textarea>

            <button id="submitBtn" onclick="getRecommendation()">Get AI Recommendation</button>
        </div>

        <div class="loading" id="loading">Analyzing your trade query with AI...</div>

        <div class="card" id="resultCard" style="display:none;">
            <label>AI Recommendation</label>
            <div class="result" id="result"></div>
        </div>
    </div>

    <script>
        async function getRecommendation() {
            const query = document.getElementById('query').value;
            const direction = document.getElementById('direction').value;
            const btn = document.getElementById('submitBtn');
            const loading = document.getElementById('loading');
            const resultCard = document.getElementById('resultCard');
            const result = document.getElementById('result');

            if (!query.trim()) return;

            btn.disabled = true;
            loading.style.display = 'block';
            resultCard.style.display = 'none';

            try {
                const response = await fetch('/recommend', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ query, direction })
                });
                const data = await response.json();
                result.textContent = data.recommendation;
                resultCard.style.display = 'block';
            } catch (error) {
                result.textContent = 'Error: ' + error.message;
                resultCard.style.display = 'block';
            } finally {
                btn.disabled = false;
                loading.style.display = 'none';
            }
        }
    </script>
</body>
</html>
Serve the frontend and test locally

Your frontend file exists, but FastAPI doesn't know to serve it yet. You need to mount the static/ directory and add a root route that returns index.html.

  • In main.py, scroll to the bottom of the file (after the health_check function).
  • Add the following code at the end of the file:
app.mount("/static", StaticFiles(directory="static"), name="static")


@app.get("/")
async def root():
    return FileResponse("static/index.html")

What does this code do?

  • app.mount("/static", ...) tells FastAPI to serve any file in your static/ folder at the /static/ URL path.
  • The root() function responds to GET requests at / (the homepage) by returning the HTML file directly using FileResponse.

The StaticFiles and FileResponse imports are already at the top of your file from the previous step.

  • Save main.py.
  • Start the development server by running:
fastapi dev main.py

What should I see?

Your terminal will show output indicating the server is running on http://127.0.0.1:8000. The dev server auto-reloads when you make code changes.

  • Open your browser and go to http://127.0.0.1:8000.
  • Select a trade direction from the dropdown (e.g., Germany to Bangladesh).
  • Type a trade query into the text area, such as I want to export medical instruments from Germany to Bangladesh.
  • Click Get AI Recommendation.

After a few seconds, you should see a structured AI recommendation appear below the form with product categories, compliance notes, and cost optimization suggestions.

Not seeing the page or getting an error?

  • If you see a "directory not found" error, make sure the static/ folder exists and contains index.html.
  • If the recommendation fails, check that your GEMINI_API_KEY environment variable is set correctly in your terminal session.
  • If the button does nothing, open your browser's developer console (F12) and look for JavaScript errors. A mismatch between the onclick function name and the script's function name will cause a silent failure.

Help me debug why my TradeConnect frontend isn't loading or returning recommendations.

✔️ Awesome, I've got everything!

Great. Double check you've saved main.py before moving on.

ⓧ I'd like to double check the full code

import os
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from pydantic import BaseModel
from google import genai
from google.genai import types

app = FastAPI(title="TradeConnect BD-DE")

GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "your-api-key-here")
client = genai.Client(api_key=GEMINI_API_KEY)

TRADE_SYSTEM_PROMPT = """You are an expert trade advisor specializing in Bangladesh-Germany bilateral commerce.
Bangladesh-Germany bilateral trade was USD 9.81 billion in 2024.
Germany is Bangladesh's second-largest export destination (10.96% of total exports).
Key trade flows:
- Bangladesh to Germany: textiles (90%+ of exports), pharmaceuticals, leather goods, footwear
- Germany to Bangladesh: machinery ($241M), medical/optical apparatus ($68M), electronics ($62M), chemicals ($46M), food products ($16M)

When a user describes a trade they want to make, provide:
1. Recommended product categories with estimated HS code ranges
2. Key compliance and regulatory considerations (EU/Bangladesh regulations)
3. Cost optimization suggestions
4. Similar successful trade patterns

Format your response as structured JSON with keys: product_categories, compliance_notes, cost_optimization, trade_patterns."""


class TradeQuery(BaseModel):
    query: str
    direction: str = "bd_to_de"


class TradeRecommendation(BaseModel):
    query: str
    direction: str
    recommendation: str


@app.post("/recommend", response_model=TradeRecommendation)
async def get_recommendation(trade_query: TradeQuery):
    direction_label = (
        "Bangladesh to Germany"
        if trade_query.direction == "bd_to_de"
        else "Germany to Bangladesh"
    )
    user_prompt = f"Trade direction: {direction_label}\nUser query: {trade_query.query}"

    response = client.models.generate_content(
        model="gemini-2.5-flash",
        contents=user_prompt,
        config=types.GenerateContentConfig(
            system_instruction=TRADE_SYSTEM_PROMPT,
            temperature=0.3,
            max_output_tokens=1024,
        ),
    )

    return TradeRecommendation(
        query=trade_query.query,
        direction=trade_query.direction,
        recommendation=response.text,
    )


@app.get("/health")
async def health_check():
    return {"status": "healthy", "service": "TradeConnect BD-DE"}


app.mount("/static", StaticFiles(directory="static"), name="static")


@app.get("/")
async def root():
    return FileResponse("static/index.html")

Your app is working end-to-end locally. Next up, you'll containerize it with Docker so it runs the same way anywhere.

Containerize with Docker

Your trade app runs perfectly on your machine. But what happens when you move it to a cloud server with a different operating system, a different Python version, or missing dependencies?

This is the classic "works on my machine" problem. Docker solves it by packaging your app, its dependencies, and its runtime environment into a single container that runs identically everywhere. In this step, you'll containerize TradeConnect BD-DE so it's ready for deployment.

In this step, get ready to:
  • Write a Dockerfile that packages the FastAPI app.
  • Create a docker-compose.yml for easy container orchestration.
  • Build and run the container to verify it works.
Write the Dockerfile

A Dockerfile is a recipe that tells Docker exactly how to build your application's container image. It specifies the base operating system, installs dependencies, copies your code, and defines how to start the app.

  • In Cursor, create a new file called Dockerfile in the root of your tradeconnect-bd-de folder (the same level as main.py).
  • Paste the following code into Dockerfile:
FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["fastapi", "run", "main.py", "--host", "0.0.0.0", "--port", "8000"]

What does this Dockerfile do?

  • FROM python:3.12-slim starts from a lightweight Python 3.12 image. The slim variant keeps the image small by excluding build tools you don't need.
  • COPY requirements.txt . copies just the requirements file first, then RUN pip install installs dependencies. This ordering lets Docker cache the dependency layer so rebuilds are fast.
  • COPY . . copies the rest of your app code into the container.
  • CMD defines the command that runs when the container starts. It launches FastAPI in production mode on port 8000.
  • Save Dockerfile.

You won't see a visible change yet. This file will be used when you build the container in the final substep.

Next, you need a .dockerignore file. When Docker runs COPY . ., it copies everything in your project folder into the container. A .dockerignore file tells Docker which files to skip.

  • Create a new file called .dockerignore in the same folder as your Dockerfile.
  • Paste this into .dockerignore:
__pycache__
*.pyc
.git
.github
.env

Why exclude these files?

Cache files (__pycache__, *.pyc) are regenerated inside the container. Git history (.git) and GitHub Actions config (.github) aren't needed at runtime. Your .env file contains secrets that should never be baked into an image.

  • Save .dockerignore.

Don't see .dockerignore in the file explorer?

Files starting with a dot are hidden by default on most operating systems. In Cursor's Explorer sidebar, the file should still appear. If you don't see it, make sure you saved it with the exact name .dockerignore (no extra extension).

help me find my .dockerignore file in Cursor

Create the Docker Compose configuration

Docker Compose lets you define and run your container with a single command instead of typing long docker run commands with multiple flags. You define everything in a docker-compose.yml file.

  • Create a new file called docker-compose.yml in the root of your project folder (same level as Dockerfile).
  • Paste the following into docker-compose.yml:
services:
  web:
    build: .
    ports:
      - "8000:8000"
    environment:
      - GEMINI_API_KEY=${GEMINI_API_KEY}
    restart: unless-stopped

What does this configuration do?

  • build: . tells Docker Compose to build an image using the Dockerfile in the current directory.
  • ports: "8000:8000" maps port 8000 on your machine to port 8000 inside the container.
  • environment passes your GEMINI_API_KEY from your terminal's environment into the container. The container never stores the key in its image.
  • restart: unless-stopped automatically restarts the container if it crashes.
  • Save docker-compose.yml.

You won't see a change yet. The next substep builds and runs everything.

YAML indentation errors?

YAML is whitespace-sensitive. Make sure you use spaces (not tabs) for indentation. Each nested level uses exactly 2 spaces.

help me fix indentation in my docker-compose.yml

Build and run the container

With all three Docker files in place, you can now build your container image and run it. The result should behave identically to when you ran the app with fastapi dev.

  • If your fastapi dev server from the previous step is still running, stop it by pressing Ctrl+C in that terminal.
  • Make sure your GEMINI_API_KEY environment variable is set in your terminal. If you closed and reopened the terminal since Step 3, export it again by running:
export GEMINI_API_KEY=your-api-key-here
  • Replace your-api-key-here with your actual Gemini API key from Step 1.
  • Build and start the container by running:
docker compose up --build

What does this command do?

docker compose up --build reads your docker-compose.yml, builds the image from your Dockerfile, and starts the container. The --build flag ensures the image is freshly built from your latest code.

You'll see Docker downloading the Python base image, installing your dependencies, and then starting the FastAPI server. This may take a minute or two on first run.

  • Wait for the output to show a line containing Uvicorn running on http://0.0.0.0:8000.
  • Open your browser and go to http://localhost:8000.
  • Enter a trade query (like "leather goods from Bangladesh to Germany") and click Get AI Recommendation.
  • Confirm you receive an AI-generated recommendation, just like when you ran the app locally.

Container won't start or port already in use?

  • If you see a port conflict error, make sure you stopped your fastapi dev server from the previous step. Both use port 8000.
  • If you see an error about Docker not running, open Docker Desktop from your system tray and wait for it to finish starting.
  • If the app loads but AI recommendations fail, check that your GEMINI_API_KEY is exported in the same terminal where you ran docker compose up --build.

help me troubleshoot my Docker container not starting

  • When you're done testing, press Ctrl+C in the terminal to stop the container.

✔️ Awesome, I've got everything!

Great. Double check you've saved all three new files: Dockerfile, .dockerignore, and docker-compose.yml.

ⓧ I'd like to double check the full code

Here are all three Docker files for comparison.

FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["fastapi", "run", "main.py", "--host", "0.0.0.0", "--port", "8000"]
services:
  web:
    build: .
    ports:
      - "8000:8000"
    environment:
      - GEMINI_API_KEY=${GEMINI_API_KEY}
    restart: unless-stopped
__pycache__
*.pyc
.git
.github
.env

Your app is now containerized and portable. Any machine with Docker installed can run this exact same container and get identical results. Next up, you'll set up a CI/CD pipeline that automatically builds and publishes this container image every time you push code.

Set Up GitHub Actions CI/CD Pipeline

Your app is containerized and running locally. The next challenge is automation. Right now, if you make a code change, you have to manually rebuild the Docker image, re-run tests, and re-deploy.

In this step, you'll set up a CI/CD pipeline using GitHub Actions. Every time you push code, your pipeline will automatically lint it, test it, and publish a fresh Docker image to GitHub Container Registry.

In this step, get ready to:
  • Push your project code to a GitHub repository.
  • Create a CI/CD workflow that automatically lints and tests your code.
  • Add automated Docker image publishing to GitHub Container Registry.
Create a GitHub repository and push your project code

Your code needs to live in a GitHub repository for GitHub Actions to detect changes and run your pipeline.

  • Initialize a Git repository in your project folder by running these commands:
git init
git add .
git commit -m "Initial commit: TradeConnect BD-DE"

What do these commands do?

  • `git init` creates a new Git repository in your current folder.
  • `git add .` stages all your project files for the first commit.
  • `git commit` saves a snapshot of your project with a descriptive message.

Keep your API key out of the repo

If you have a .env file in your project folder containing your Gemini API key, delete it before running git add. Your API key should remain as an environment variable on your machine, not committed to a repository.

  • Open your browser and go to github.com.
  • Click the + icon in the top-right corner.
  • Select New repository.
  • Enter tradeconnect-bd-de as the repository name.
  • Select Public for visibility.
  • Leave all other options unchecked (no README, no .gitignore, no license).
  • Click Create repository.

Why public?

Public repositories get unlimited free GitHub Actions minutes. Private repositories are limited to 2,000 minutes per month on the free plan. Making the repo public also makes it easier to pull the Docker image later during deployment.

  • Back in your terminal, connect your local repo to GitHub and push your code by running these commands:
git remote add origin [[YOUR_REPO_URL="https://github.com/your-username/tradeconnect-bd-de.git"]]
git branch -M main
git push -u origin main

Where do I find my repository URL?

After creating the repository, GitHub shows a quick setup page with your repository URL. Copy the HTTPS URL displayed there. It follows the pattern https://github.com/your-username/tradeconnect-bd-de.git.

  • Refresh your GitHub repository page in the browser.
  • You should see all your project files listed: main.py, requirements.txt, Dockerfile, docker-compose.yml, and the static/ folder.

Push failed or asking for credentials?

  • If Git asks for a username and password, GitHub no longer accepts passwords for HTTPS. Generate a Personal Access Token at Settings > Developer settings > Personal access tokens > Tokens (classic) and use that as your password.
  • If you see "remote origin already exists", run git remote set-url origin <your-url> instead.

Having trouble pushing to GitHub?

Write a GitHub Actions workflow that runs linting and tests

A GitHub Actions workflow is a YAML file that tells GitHub what to do every time you push code. Your first job will install Python, run a linter to catch style issues, and run a health check test against your FastAPI app.

  • Create the workflow directory by running this command:
mkdir -p .github/workflows
  • Create a new file called ci-cd.yml inside .github/workflows/ and paste the following code:
name: CI/CD Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Install dependencies
        run: pip install -r requirements.txt

      - name: Run linting
        run: pip install ruff && ruff check .

      - name: Run health check test
        run: |
          pip install httpx
          python -c "
          from fastapi.testclient import TestClient
          from main import app
          client = TestClient(app)
          response = client.get('/health')
          assert response.status_code == 200
          assert response.json()['status'] == 'healthy'
          print('Health check passed!')
          "

What does this workflow do?

  • `on: push / pull_request` triggers the workflow whenever code is pushed to main or a pull request targets it.
  • `actions/checkout@v4` clones your repository so the runner has access to your files.
  • `actions/setup-python@v5` installs Python 3.12 on the runner.
  • `ruff check .` is a fast Python linter that catches style and syntax issues before they reach production.
  • The health check test imports your FastAPI app, hits the /health endpoint, and asserts the response is valid. This confirms your app starts without crashing.
  • Save .github/workflows/ci-cd.yml.

File not in the right location?

The path must be exactly .github/workflows/ci-cd.yml from your project root. If the folders are named differently (like github without the dot, or workflow without the s), GitHub will not detect the workflow.

Need help setting up the workflow file?

Add the Docker build-and-push job

The second job builds your Docker image and publishes it to GitHub Container Registry (ghcr.io). This means every successful push to main produces a fresh, deployable container image automatically.

  • In .github/workflows/ci-cd.yml, find the end of the test job (after the last line of the health check step).
  • Add the following build-and-push job below it, at the same indentation level as test::
  build-and-push:
    needs: test
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push Docker image
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository }}:latest

What does this job do?

  • `needs: test` means this job only runs after the test job passes. If linting or tests fail, the image is never published.
  • The if condition ensures images are only pushed on direct pushes to main. Pull requests trigger tests but skip the image push.
  • `permissions: packages: write` grants the workflow token permission to push container images to ghcr.io.
  • `docker/login-action@v3` authenticates with GitHub Container Registry using the built-in GITHUB_TOKEN secret. No manual secret configuration needed.
  • `docker/build-push-action@v6` builds the image using your Dockerfile and pushes it tagged as ghcr.io/<your-repo>:latest.
  • Save .github/workflows/ci-cd.yml.
  • Push the workflow file to GitHub to trigger your pipeline by running these commands:
git add .
git commit -m "Add CI/CD workflow"
git push
  • In your browser, go to your GitHub repository.
  • Click the Actions tab at the top.
  • You should see a workflow run named "Add CI/CD workflow" appear. Click on it to watch the progress.
  • Wait for both the test and build-and-push jobs to show green checkmarks. This takes 1-2 minutes.

Workflow failing?

  • If the test job fails on "Run linting", ruff found a style issue in your code. Click the failed step to see which file and line to fix.
  • If the build-and-push job fails with a permission error, make sure your repository is Public and the permissions block in your YAML includes packages: write.
  • If the workflow does not appear at all, double-check the file path is .github/workflows/ci-cd.yml (with the dot prefix on .github).

My GitHub Actions workflow is failing and I need help debugging it.

✔️ Awesome, I've got everything!

Great. Double check you've saved .github/workflows/ci-cd.yml and that both jobs show green checkmarks in the Actions tab.

ⓧ I'd like to double check the full code

Here is the complete .github/workflows/ci-cd.yml file:

name: CI/CD Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Install dependencies
        run: pip install -r requirements.txt

      - name: Run linting
        run: pip install ruff && ruff check .

      - name: Run health check test
        run: |
          pip install httpx
          python -c "
          from fastapi.testclient import TestClient
          from main import app
          client = TestClient(app)
          response = client.get('/health')
          assert response.status_code == 200
          assert response.json()['status'] == 'healthy'
          print('Health check passed!')
          "

  build-and-push:
    needs: test
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push Docker image
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository }}:latest

Your pipeline is live. Every push to main now triggers automated testing and publishes a fresh Docker image. Next up, you'll deploy that image to a real cloud server so anyone can access your trade platform.

Deploy to AWS EC2

Your CI/CD pipeline is pushing a fresh Docker image to GitHub Container Registry on every commit. The final piece is getting that container running on a server the world can reach.

In this step, you will launch an EC2 instance on AWS, install Docker on it, pull your container image, and serve the app on a public IP address.

In this step, get ready to:
  • Launch a t3.micro EC2 instance with a security group that allows SSH and HTTP traffic.
  • SSH into the instance, install Docker, and pull your container image from ghcr.io.
  • Run the container and access your app in the browser via the instance's public IP.
Create an AWS account and launch an EC2 instance

AWS provides cloud servers you can rent on demand. You need an account and a single virtual machine to host your container.

  • Open your browser and go to https://aws.amazon.com.
  • Click Create an AWS Account and follow the sign-up prompts.
  • You will need to enter a credit card. AWS provides up to $200 in free credits for new accounts created after July 15, 2025. The t3.micro instance is covered by these credits.
  • Once signed in, use the search bar at the top of the AWS Console and type EC2.
  • Click EC2 to open the EC2 Dashboard.

What is EC2?

EC2 (Elastic Compute Cloud) is AWS's virtual server service. You choose the hardware specs, the operating system, and the region. It launches in seconds and you only pay while it runs.

  • Click Launch instance.
  • Under Name, enter tradeconnect-bd-de.
  • Under Application and OS Images, select Amazon Linux 2023 AMI (it should be the default).
  • Under Instance type, choose t3.micro (free tier eligible).
  • Under Key pair (login), click Create new key pair.
  • Name it tradeconnect-key, keep the defaults (RSA, .pem), and click Create key pair.
  • A .pem file will download automatically. Save it somewhere safe. You will use it to SSH into your instance.

Now configure the security group to allow incoming traffic on the ports your app needs.

  • Under Network settings, click Edit.
  • Make sure Allow SSH traffic from is checked (port 22).
  • Click Add security group rule.
  • Set Type to Custom TCP.
  • Set Port range to 8000.
  • Set Source type to Anywhere (0.0.0.0/0).
  • Click Launch instance.

Why port 8000?

Your FastAPI container listens on port 8000. The security group acts as a firewall. Without explicitly allowing port 8000, no browser traffic could reach your app even though the container is running.

  • Wait for the instance state to show Running (usually takes 30-60 seconds).
  • Click on your instance ID to open its details page.
  • Copy the Public IPv4 address shown in the instance summary. You will need it for the SSH command and to access your app later.

Instance not launching?

If you see an error about instance type availability, try a different availability zone by going back and choosing a different subnet. The t3.micro type is available in all major regions.

If you see a vCPU limit error, your account may need to request a service quota increase for On-Demand instances.

Help me troubleshoot my EC2 launch failure.

SSH into the instance and install Docker

Your EC2 instance is a blank Linux server. You need to connect to it via SSH, then install Docker so it can run your container.

  • Open a terminal on your local machine.
  • First, restrict the permissions on your key file by running this command:
chmod 400 [[PEMPATH="path/to/tradeconnect-key.pem"]]

What does this command do?

SSH refuses to use key files that are readable by other users. The chmod 400 command makes the file read-only for you and invisible to everyone else.

  • Connect to your EC2 instance by running this command:
ssh -i [[PEMPATH="path/to/tradeconnect-key.pem"]] ec2-user@[[EC2_PUBLIC_IP="your EC2 public IPv4 address"]]
  • When prompted "Are you sure you want to continue connecting?", type yes and press Enter.

You should see a welcome message from the Amazon Linux instance. Your terminal prompt now shows ec2-user@ followed by an IP address.

Windows users without chmod

On Windows, right-click your .pem file, go to Properties then Security then Advanced. Remove all users except your own account, then disable inheritance.

  • Update system packages by running this command:
sudo yum update -y
  • Install Docker by running this command:
sudo yum install -y docker
  • Start the Docker service by running:
sudo service docker start
  • Add your user to the Docker group so you can run Docker commands without sudo:
sudo usermod -a -G docker ec2-user
  • Apply the new group permissions by logging out and back in. Run:
exit
  • Reconnect with the same SSH command:
ssh -i [[PEMPATH="path/to/tradeconnect-key.pem"]] ec2-user@[[EC2_PUBLIC_IP="your EC2 public IPv4 address"]]
  • Verify Docker is working by running:
docker info

You should see Docker version information and details about the storage driver. This confirms Docker is installed and your user can run it without sudo.

Seeing "Cannot connect to the Docker daemon"?

Make sure you disconnected and reconnected your SSH session after running the usermod command. The group change only takes effect on a new login.

If it still fails after reconnecting, try rebooting the instance with sudo reboot and wait a minute before reconnecting.

Help me fix Docker permissions on my EC2 instance.

Pull your container image and run the app

Your Docker image was pushed to GitHub Container Registry in the previous step. Before you can pull it on EC2, you need to make the package publicly accessible.

  • In your browser, go to your GitHub repository page.
  • Click Packages in the right sidebar of your repository.
  • Click on your package name.
  • Click Package settings (on the right side of the package landing page).
  • Scroll down to the Danger Zone section.
  • Click Change visibility and select Public.
  • Type the package name to confirm, then click I understand the consequences, change package visibility.

Why make the package public?

By default, GitHub Container Registry packages are private. Your EC2 instance has no GitHub credentials, so it cannot pull a private image. Making the package public allows anonymous pulls without authentication.

  • Back in your EC2 SSH session, pull the container image by running:
docker pull ghcr.io/[[GITHUB_REPO="your-username/your-repo-name"]]:latest

You should see Docker downloading multiple layers. This is your app image being transferred from GitHub to your EC2 instance.

  • Run the container with your Gemini API key by running:
docker run -d -p 8000:8000 -e GEMINI_API_KEY=your-api-key-here ghcr.io/[[GITHUB_REPO="your-username/your-repo-name"]]:latest

What does this command do?

  • `-d` runs the container in the background (detached mode).
  • `-p 8000:8000` maps port 8000 on the EC2 instance to port 8000 inside the container.
  • `-e GEMINI_API_KEY=...` passes your API key as an environment variable to the container.
  • The last argument is the image name to run.
  • Verify the container is running:
docker ps

You should see one container listed with status Up and port mapping 0.0.0.0:8000->8000/tcp.

  • Open your browser and navigate to:
http://[[EC2_PUBLIC_IP="your EC2 public IPv4 address"]]:8000

You should see the TradeConnect BD-DE frontend. Try entering a trade query to confirm the AI integration is working end-to-end.

Page not loading in the browser?

Check that your security group allows inbound traffic on port 8000. In the EC2 console, click your instance, then the Security tab. Verify port 8000 has a rule with source 0.0.0.0/0.

Make sure you are using http:// (not https://) in the URL. There is no SSL certificate on this instance.

Run docker logs followed by the container ID (from docker ps) to check for startup errors inside the container.

Help me debug why my EC2 app isn't reachable in the browser.

Your trade recommendation platform is now live on the internet. Anyone with the public IP can submit a trade query and get AI-powered recommendations. You have gone from local code to a fully deployed, containerized application with an automated CI/CD pipeline behind it.

Secret mission

Add a Route Comparison Endpoint

Your trade platform can recommend products, but what about logistics? Build a /compare endpoint that uses Gemini to compare sea, air, and multimodal shipping routes between Bangladesh and Germany with estimated costs, transit times, and reliability scores.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. Your EC2 instance is covered by AWS free tier credits ($200 for new accounts), but those credits expire after 6 months or when exhausted.

Cost warning

Your t3.micro EC2 instance is covered by AWS free tier credits. Once credits expire, a running t3.micro instance will start incurring hourly compute charges. If you are not actively using the app, stop or terminate the instance to avoid unexpected costs.

Resources you used:

  • AWS EC2 instance (t3.micro running the TradeConnect BD-DE container).
  • Google AI Studio API key (free tier, no ongoing charges).
  • GitHub Container Registry image on ghcr.io (free for public repositories).
  • Local project files on your Desktop.

Keep everything running

No action needed. Choose this if you are still actively building or want to show the live app to others.

  • Your EC2 instance will continue consuming free tier credits while running. Monitor usage in the AWS Billing dashboard.
  • Your Google AI Studio API key remains active on the free tier with no charges.
  • Your GitHub Container Registry image stays published at no cost for public repos.
  • Your local project files remain on your Desktop.

Pause - I'll come back to this later

Stop the EC2 instance to pause compute charges while keeping your setup intact. You can restart it later.

  • Open the EC2 console at https://console.aws.amazon.com/ec2/.
  • In the navigation pane, choose Instances.
  • Select your TradeConnect instance.
  • Choose Instance state, then Stop instance.
  • Choose Stop when prompted for confirmation.
  • Your instance will stop incurring compute charges. Minimal EBS storage charges may still apply.
  • To restart later, select the instance and choose Instance state, then Start instance.

Delete - I don't want to use this again

Remove all project resources to ensure zero ongoing charges and a clean slate.

Terminate the EC2 instance
  • Open the EC2 console at https://console.aws.amazon.com/ec2/.
  • In the navigation pane, choose Instances.
  • Select your TradeConnect instance, and choose Instance state, Terminate (delete) instance.
  • Choose Terminate (delete) when prompted for confirmation.
  • The instance will briefly show a terminated state, then disappear from the console.
Delete the Google AI Studio API key
  • Go to https://aistudio.google.com/apikey.
  • Find the API key you created for this project.
  • Click the delete icon next to the key and confirm deletion.
Delete the GitHub Container Registry image
  • On GitHub, click your profile picture in the top right corner, then click Your profile.
  • In the header of your profile page, click the Packages tab.
  • Click the name of the container package for this project.
  • On the package landing page, click Package settings on the right-hand side.
  • At the bottom of the page under "Danger zone", click Delete this package.
  • Type the package name to confirm, then click I understand the consequences, delete this package.
Delete local project files
  • Delete the project folder from your Desktop by running this command:

macOS

rm -rf ~/Desktop/tradeconnect-bd-de

Windows

Remove-Item -Recurse -Force ~\Desktop\tradeconnect-bd-de
Delete the GitHub repository (optional)
  • If you no longer need the source code on GitHub, go to your repository page.
  • Click Settings in the repository navigation bar.
  • Scroll to the bottom under "Danger Zone" and click Delete this repository.
  • Follow the confirmation prompts to permanently delete the repository.

That's a Wrap!

That's a Wrap!

Nice work! You've just built a full-stack AI-powered trade recommendation platform and deployed it to the cloud with a professional DevOps pipeline.

You've learned how to:

  • Built a FastAPI web application with Google Gemini AI integration that generates structured trade recommendations for Bangladesh-Germany commerce.
  • Containerized the application with Docker and automated builds with a GitHub Actions CI/CD pipeline that lints, tests, and publishes images to GitHub Container Registry on every push.
  • Deployed the containerized app to an AWS EC2 instance accessible via a public URL, proving you can take code from local development all the way to production.
  • Secret Mission: Added a /compare endpoint that compares shipping routes using advanced prompt engineering with Gemini.

Ready to quiz yourself?