This is your master deployment playbook — from a blank Ubuntu 24.04 EC2 instance to a fully live production application reachable on the public internet.
We use Ubuntu 24.04 LTS because Python 3.12 is the native default version on this OS. No pyenv compilation, no hacky wrappers — everything works smoothly out of the box using the standard package manager.
⚠️ Why Ubuntu 24.04 and not the latest Ubuntu? Chainlit, FastAPI, Starlette, and LangGraph are tested against Python 3.10–3.12. Newer Ubuntu releases (25.04+) ship Python 3.13 or 3.14, where unpinned dependencies can pull in incompatible versions ofstarlette/anyioand the app will crash withanyio.NoEventLoopErroror similar. Stay on 24.04 LTS.
Before touching the server, make sure you have all of these ready on your local Windows machine:
- AMI: Ubuntu Server 24.04 LTS (HVM), 64-bit (x86)
- Instance type:
t3.medium - Storage: At least 20 GB gp3
- Key pair: Create or reuse one — download the
.pemfile and keep it safe
In the AWS console, edit your instance's Security Group to allow these inbound rules. Without this step you will not be able to SSH in or reach the app from a browser.
| Type | Protocol | Port | Source | Purpose |
|---|---|---|---|---|
| SSH | TCP | 22 | My IP | Your terminal access |
| HTTP | TCP | 80 | 0.0.0.0/0 | Public app access |
| HTTPS | TCP | 443 | 0.0.0.0/0 | Optional, for future SSL |
From the EC2 console, copy these two values for your instance:
- Public IPv4 DNS — looks like
ec2-43-xxx-xxx-xxx.ap-south-1.compute.amazonaws.com - Public IPv4 address — looks like
43.xxx.xxx.140
You'll use the DNS name in ssh commands and the IP address in your browser.
If you skip this, ssh will refuse to use the key with a "permissions are too open" error.
Open PowerShell (not WSL), cd to the folder containing the .pem file, then run:
icacls .\securelife-mcp-keypair.pem /inheritance:r
icacls .\securelife-mcp-keypair.pem /grant:r "$($env:USERNAME):(R)"This removes inherited permissions and grants only your user read access.
In the project root (same folder as the .pem file), create a file called .env with your actual API keys. Use .env.example as a template:
OPENAI_API_KEY=sk-proj-...your-real-key-here...
# Optional — only if you want LangSmith tracing
# LANGSMITH_API_KEY=ls-...your-real-key-here...
# LANGSMITH_TRACING=true
# LANGSMITH_PROJECT=securelife-mcp-chainlit-ec2
⚠️ Never commit this file to GitHub. It's already in.gitignore.
From PowerShell, in the project root:
ssh -i "securelife-mcp-keypair.pem" ubuntu@ec2-XX-XX-XX-XX.region.compute.amazonaws.comThe first time, you'll see a prompt: Are you sure you want to continue connecting (yes/no)? — type yes. You should now see a prompt like ubuntu@ip-172-31-xx-xx:~$. You're in. 🎉
On the EC2 instance, update the system and install Python 3.12 (native), SQLite, Git, and Nginx.
sudo apt update && sudo apt upgrade -y
# Install Python 3.12 virtual environment tools, SQLite, Nginx, and Git
sudo apt install -y python3-venv python3-pip python3-dev sqlite3 nginx git curlVerify you got Python 3.12 (critical — if it shows 3.13 or 3.14, stop and confirm you're really on Ubuntu 24.04):
python3 --version
# Expected: Python 3.12.xIf you see anything other than 3.12, the rest of this playbook will not work reliably. Re-launch the instance with the correct AMI.
Clone your project into the ubuntu home directory.
cd ~
git clone https://github.com/prashant9501/SecureLife-MCP-Project.git
cd SecureLife-MCP-Project
# Confirm the structure
ls
# You should see: README.md SecureLife_claims.db requirements.txt securelife_client_app securelife_mcp_serverℹ️ If the repo is private, you'll need a GitHub Personal Access Token and use the URL form
https://<TOKEN>@github.com/prashant9501/SecureLife-MCP-Project.git.
Since Python 3.12 is the system default, we use python3 to create the environment in the project root (not inside the client app subfolder — both the MCP server and the Chainlit client share this single venv).
# Stay in the project root: ~/SecureLife-MCP-Project
pwd
# Should print: /home/ubuntu/SecureLife-MCP-Project
# Create the virtual environment
python3 -m venv .venv
# Activate it
source .venv/bin/activate
# Upgrade pip
pip install --upgrade pip
# Install dependencies — requirements.txt lives in the PROJECT ROOT
pip install -r requirements.txt
⚠️ Common mistake: Older versions of this guide saidcd securelife_client_app && pip install -r requirements.txt. Therequirements.txtis actually at the project root, not insidesecurelife_client_app/. Runpip installfrom the root.
Verify the install:
pip list | grep -E "chainlit|langgraph|fastmcp|langchain-openai"You should see all four packages listed.
scp runs locally and pushes the file up.
Navigate to where your .env and .pem files live, then securely copy the .env file to the EC2 instance. (Replace <YOUR_EC2_DNS> with your instance's Public IPv4 DNS).
scp -i "securelife-mcp-keypair.pem" .env ubuntu@<YOUR_EC2_DNS>:~/SecureLife-MCP-Project/securelife_client_app/.envOnce the transfer reaches 100%, switch back to your EC2 SSH terminal and verify the file arrived and contains your real key (not the placeholder):
cat ~/SecureLife-MCP-Project/.envYou should see your actual OPENAI_API_KEY=sk-proj-... value.
💡 Why goes the
.envintosecurelife_client_app/? Because agent.py callsload_dotenv()from that directory. The MCP server doesn't need API keys — it only talks to SQLite.
Before wrapping the app in systemd services, prove that each piece runs cleanly on its own. This saves hours of debugging later.
In your current SSH session (with the venv still activated):
cd ~/SecureLife-MCP-Project/securelife_mcp_server
python server.pyExpected output:
🚀 Starting SecureLife MCP Server on port 8765...
Press Ctrl+C to stop. If you see FileNotFoundError: SecureLife_claims.db not found, your DB file is missing — confirm with ls ~/SecureLife-MCP-Project/SecureLife_claims.db.
Open a second SSH session to the same instance (so the MCP server can keep running). In the new session:
cd ~/SecureLife-MCP-Project
source .venv/bin/activate
# Start the MCP server in the background
cd securelife_mcp_server
nohup python server.py > /tmp/mcp.log 2>&1 &
# Wait a moment, then start Chainlit
cd ../securelife_client_app
chainlit run app.py --host 127.0.0.1 --port 8000Expected output: Your app is available at http://127.0.0.1:8000 (no traceback).
Press Ctrl+C to stop Chainlit. Then kill the background MCP server:
pkill -f "python server.py"If both pieces ran cleanly, you're ready to make them permanent with systemd.
We will create two background services so your app starts automatically on boot and restarts if it crashes.
sudo nano /etc/systemd/system/securelife-backend.servicePaste this configuration:
[Unit]
Description=SecureLife MCP Backend Server
After=network.target
[Service]
User=ubuntu
WorkingDirectory=/home/ubuntu/SecureLife-MCP-Project/securelife_mcp_server
ExecStart=/home/ubuntu/SecureLife-MCP-Project/.venv/bin/python server.py
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target(Save and exit: Ctrl+O, Enter, Ctrl+X)
sudo nano /etc/systemd/system/securelife-frontend.servicePaste this configuration:
[Unit]
Description=SecureLife Chainlit Frontend App
After=network.target securelife-backend.service
Requires=securelife-backend.service
[Service]
User=ubuntu
WorkingDirectory=/home/ubuntu/SecureLife-MCP-Project/securelife_client_app
ExecStart=/home/ubuntu/SecureLife-MCP-Project/.venv/bin/chainlit run app.py --host 127.0.0.1 --port 8000
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target(Save and exit: Ctrl+O, Enter, Ctrl+X)
💡 Why
Requires=? It tells systemd the frontend depends on the backend — if the backend stops, the frontend stops with it, preventing zombie states where the UI tries to call an unavailable MCP server.
Reload the system daemon to recognize the new files, enable them to run on boot, and start them.
sudo systemctl daemon-reload
# Enable for auto-start on reboot
sudo systemctl enable securelife-backend.service
sudo systemctl enable securelife-frontend.service
# Start them now
sudo systemctl start securelife-backend.service
sudo systemctl start securelife-frontend.serviceVerify both are running cleanly:
sudo systemctl status securelife-backend.service
sudo systemctl status securelife-frontend.serviceYou should see green active (running) for both. Press q to exit the status view.
If either shows failed or activating (auto-restart), jump to the Troubleshooting section below before continuing.
Finally, we route external traffic on port 80 to your Chainlit app on port 8000, ensuring WebSockets (which Chainlit uses for streaming responses) are handled correctly.
sudo nano /etc/nginx/sites-available/securelifePaste this configuration:
server {
listen 80;
server_name _;
# Increase max body size for potential file uploads
client_max_body_size 10M;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Mandatory for streaming Chainlit WebSockets
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
}
}(Save and exit: Ctrl+O, Enter, Ctrl+X)
Enable the configuration and restart Nginx:
# Create symlink to enable the site
sudo ln -s /etc/nginx/sites-available/securelife /etc/nginx/sites-enabled/
# Remove default Nginx welcome page
sudo rm -f /etc/nginx/sites-enabled/default
# Test Nginx syntax — must say "syntax is ok" and "test is successful"
sudo nginx -t
# Restart Nginx to apply
sudo systemctl restart nginx
# Verify Nginx is active
sudo systemctl status nginxOpen a browser tab and navigate to your EC2 Public IPv4 address (the plain IP, not the DNS):
http://43.205.214.140
You should see the welcome message:
👋 Welcome to SecureLife Claims Processing Hub
Try one of the test prompts from the README to confirm the full pipeline works:
CLM-2025-0001 | Routine health checkup reimbursement request.
You should see five collapsible steps stream in (triage → doc_verifier → fraud_analyst → decision_maker → compliance_auditor) followed by a Final Evaluation Summary.
# Live tail the logs for either service
sudo journalctl -u securelife-backend.service -f
sudo journalctl -u securelife-frontend.service -f
# Or the last 50 lines without following
sudo journalctl -u securelife-frontend.service -n 50 --no-pagerPress Ctrl+C to exit a -f (follow) tail.
| Symptom in logs | Cause | Fix |
|---|---|---|
FileNotFoundError: SecureLife_claims.db |
DB missing | ls ~/SecureLife-MCP-Project/SecureLife_claims.db — re-clone if absent |
OPENAI_API_KEY missing or 401 Unauthorized |
.env not uploaded or wrong key |
Re-run Step 4 scp |
anyio.NoEventLoopError |
Python 3.13+ pulled incompatible deps | Confirm python3 --version is 3.12.x; rebuild venv |
Address already in use: 8000 |
Old Chainlit process still running | sudo lsof -i :8000 then kill <PID> |
502 Bad Gateway from browser |
Chainlit service not running | sudo systemctl status securelife-frontend.service |
| Can't reach the IP from browser | Security Group missing HTTP rule | Re-check Step 0.2 |
sudo systemctl restart securelife-backend.service
sudo systemctl restart securelife-frontend.service
sudo systemctl restart nginxsudo ss -tlnp | grep -E "80|8000|8765"You should see Nginx on :80, Chainlit on 127.0.0.1:8000, and FastMCP on :8765.
After your initial deploy, to pull in new code changes from GitHub:
cd ~/SecureLife-MCP-Project
# Pull the latest code
git pull
# Activate venv and update dependencies (in case requirements.txt changed)
source .venv/bin/activate
pip install -r requirements.txt
# Restart the services to load the new code
sudo systemctl restart securelife-backend.service
sudo systemctl restart securelife-frontend.service
# Confirm they're still healthy
sudo systemctl status securelife-frontend.service💡 If you only changed the frontend, you don't need to restart the backend — and vice versa.
For production use, you'll want HTTPS. This requires a domain name pointed at your EC2 IP (Route53, GoDaddy, Namecheap, etc.).
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.example.comCertbot will automatically:
- Obtain a free SSL certificate
- Update your Nginx config to listen on
:443with TLS - Set up auto-renewal
After this, your app is reachable at https://your-domain.example.com.
If you want to fully remove the app from the instance:
sudo systemctl stop securelife-frontend.service securelife-backend.service
sudo systemctl disable securelife-frontend.service securelife-backend.service
sudo rm /etc/systemd/system/securelife-frontend.service /etc/systemd/system/securelife-backend.service
sudo systemctl daemon-reload
sudo rm /etc/nginx/sites-enabled/securelife /etc/nginx/sites-available/securelife
sudo systemctl restart nginx
rm -rf ~/SecureLife-MCP-ProjectTo stop billing entirely, terminate the EC2 instance from the AWS console.