Skip to content

Commit aaf5635

Browse files
authored
Merge pull request #3 from matsjfunke/docker
2 parents d53927d + f2e6fc9 commit aaf5635

File tree

6 files changed

+347
-0
lines changed

6 files changed

+347
-0
lines changed

.dockerignore

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Git
2+
.git
3+
.gitignore
4+
.gitattributes
5+
6+
# Docker
7+
Dockerfile*
8+
docker-compose*
9+
.dockerignore
10+
11+
# Documentation
12+
*.md
13+
docs/
14+
15+
# Python
16+
__pycache__/
17+
*.py[cod]
18+
*$py.class
19+
*.so
20+
.Python
21+
build/
22+
develop-eggs/
23+
dist/
24+
downloads/
25+
eggs/
26+
.eggs/
27+
lib/
28+
lib64/
29+
parts/
30+
sdist/
31+
var/
32+
wheels/
33+
*.egg-info/
34+
.installed.cfg
35+
*.egg
36+
MANIFEST
37+
38+
# Virtual environments
39+
.env
40+
.venv
41+
env/
42+
venv/
43+
ENV/
44+
env.bak/
45+
venv.bak/
46+
.conda/
47+
48+
# IDEs
49+
.cursor/
50+
.vscode/
51+
.idea/
52+
*.swp
53+
*.swo
54+
*~
55+
56+
# OS
57+
.DS_Store
58+
.DS_Store?
59+
._*
60+
.Spotlight-V100
61+
.Trashes
62+
ehthumbs.db
63+
Thumbs.db
64+
65+
# Testing
66+
.tox/
67+
.nox/
68+
.coverage
69+
.pytest_cache/
70+
htmlcov/
71+
.cache
72+
tests/
73+
74+
# Jupyter Notebook
75+
.ipynb_checkpoints
76+
77+
# Environment variables
78+
.env*
79+
env.prod.template
80+
81+
# Logs
82+
*.log
83+
logs/
84+
85+
# Temporary files
86+
*.tmp
87+
*.temp
88+
.tmp/
89+
.temp/

.github/workflows/deploy.yml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
name: Deploy to VPS
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
8+
jobs:
9+
deploy:
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- name: Checkout code
14+
uses: actions/checkout@v4
15+
16+
- name: Deploy to VPS
17+
uses: appleboy/ssh-action@v1.0.3
18+
with:
19+
host: ${{ secrets.VPS_HOST }}
20+
username: ${{ secrets.VPS_USERNAME }}
21+
key: ${{ secrets.VPS_SSH_KEY }}
22+
passphrase: ${{ secrets.VPS_SSH_KEY_PASSPHRASE }}
23+
script: |
24+
cd /opt/paperclip-mcp
25+
git fetch origin main
26+
git reset --hard origin/main
27+
28+
echo "🔄 Stopping containers..."
29+
docker-compose down
30+
31+
echo "🏗️ Building containers..."
32+
docker-compose build --no-cache
33+
34+
echo "🚀 Starting containers..."
35+
docker-compose up -d
36+
37+
echo "⏳ Waiting for containers to start..."
38+
sleep 10
39+
40+
echo "📊 Container status:"
41+
docker-compose ps --format "table {{.Service}}\t{{.Status}}\t{{.Ports}}"
42+
43+
echo "📋 Recent logs from all services (filtered):"
44+
docker-compose logs --tail=30 | grep -E "(ERROR|WARN|INFO|Ready|Starting|Listening)" | head -50
45+
46+
echo "🧹 Cleaning up..."
47+
docker system prune -f
48+
49+
- name: Show specific service logs on failure
50+
if: failure()
51+
uses: appleboy/ssh-action@v1.0.3
52+
with:
53+
host: ${{ secrets.VPS_HOST }}
54+
username: ${{ secrets.VPS_USERNAME }}
55+
key: ${{ secrets.VPS_SSH_KEY }}
56+
passphrase: ${{ secrets.VPS_SSH_KEY_PASSPHRASE }}
57+
script: |
58+
cd /opt/paperclip-mcp
59+
echo "🔍 Filtered logs for debugging:"
60+
echo "--- Traefik status ---"
61+
docker-compose logs --tail=50 traefik | grep -E "(ERROR|WARN|Ready|Starting|tls|certificate)" | head -30
62+
echo "--- Paperclip MCP status ---"
63+
docker-compose logs --tail=50 paperclip-mcp | grep -E "(ERROR|WARN|Ready|Starting|Listening|Build)" | head -30

.github/workflows/ping-server.yml

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
name: Ping MCP Server
2+
3+
on:
4+
schedule:
5+
# Run once per day at 10:00 UTC
6+
- cron: "0 10 * * *"
7+
workflow_dispatch:
8+
9+
jobs:
10+
ping:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout code
15+
uses: actions/checkout@v4
16+
17+
- name: Set up Python
18+
uses: actions/setup-python@v4
19+
with:
20+
python-version: "3.11"
21+
22+
- name: Install dependencies
23+
run: |
24+
python -m pip install --upgrade pip
25+
pip install fastmcp==2.11.0
26+
27+
- name: Create ping script
28+
run: |
29+
cat > ping_server.py << 'EOF'
30+
import asyncio
31+
import sys
32+
import os
33+
from datetime import datetime
34+
from fastmcp.client.client import Client
35+
36+
async def ping_server():
37+
server_url = 'https://paperclip.matsjfunke.com/mcp'
38+
39+
print(f"🏓 Pinging MCP server at: {server_url}")
40+
print(f"⏰ Timestamp: {datetime.now().isoformat()}")
41+
42+
try:
43+
# Create client instance
44+
client = Client(server_url)
45+
46+
# Connect and ping
47+
async with client:
48+
print("✅ Successfully connected to server")
49+
50+
# Send ping
51+
ping_result = await client.ping()
52+
53+
if ping_result:
54+
print("🎯 Ping successful! Server is responsive")
55+
return True
56+
else:
57+
print("❌ Ping failed! Server did not respond properly")
58+
return False
59+
60+
except Exception as e:
61+
print(f"💥 Error connecting to server: {str(e)}")
62+
print(f"🔧 Error type: {type(e).__name__}")
63+
return False
64+
65+
if __name__ == "__main__":
66+
result = asyncio.run(ping_server())
67+
if not result:
68+
sys.exit(1)
69+
EOF
70+
71+
- name: Run ping test
72+
run: python ping_server.py
73+
74+
- name: Report ping failure
75+
if: failure()
76+
run: |
77+
echo "🚨 Server ping failed!"
78+
echo "⚠️ This could indicate:"
79+
echo " - Server is down or not responding"
80+
echo " - Network connectivity issues"
81+
echo " - Server is overloaded"
82+
echo " - Configuration problems"
83+
echo ""
84+
echo "🔍 Check the deploy workflow and server logs for more details"
85+
86+
- name: Report ping success
87+
if: success()
88+
run: |
89+
echo "✅ Server ping successful!"
90+
echo "🟢 MCP server is healthy and responsive"

Dockerfile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
FROM python:3.12-slim-bullseye
2+
3+
WORKDIR /app
4+
5+
COPY requirements.txt .
6+
RUN pip install --no-cache-dir -r requirements.txt
7+
8+
# Update sources list and install packages (assuming these are needed for your app)
9+
RUN apt-get update && \
10+
apt-get upgrade -y && \
11+
apt-get install -y --no-install-recommends \
12+
libgl1-mesa-glx \
13+
libglib2.0-0 \
14+
&& apt-get clean && \
15+
rm -rf /var/lib/apt/lists/*
16+
17+
RUN pip install --upgrade pip
18+
COPY ./src .
19+
20+
EXPOSE 8000

docker-compose.prod.yml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
version: "3.8"
2+
3+
services:
4+
traefik:
5+
image: traefik:v3.0
6+
container_name: traefik
7+
command:
8+
- "--api.insecure=false" # Disable insecure API dashboard for production security
9+
- "--providers.docker=true" # Auto-discover services via Docker labels
10+
# Only expose services that explicitly set traefik.enable=true (security best practice)
11+
- "--providers.docker.exposedbydefault=false"
12+
- "--entrypoints.web.address=:80" # HTTP entrypoint redirects to HTTPS
13+
- "--entrypoints.websecure.address=:443" # HTTPS entrypoint
14+
- "--certificatesresolvers.myresolver.acme.tlschallenge=true" # Automatic SSL certificate generation via Let's Encrypt TLS challenge
15+
- "--certificatesresolvers.myresolver.acme.email=mats.funke@gmail.com" # Email required for Let's Encrypt certificate notifications and recovery
16+
- "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json" # SSL certificates persist across container restarts
17+
# Force HTTP to HTTPS redirect for security (all traffic must be encrypted)
18+
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
19+
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
20+
ports:
21+
- "80:80"
22+
- "443:443"
23+
volumes:
24+
- /var/run/docker.sock:/var/run/docker.sock:ro
25+
- traefik_letsencrypt:/letsencrypt # SSL certificates persist across container restarts
26+
networks:
27+
- web
28+
restart: unless-stopped
29+
30+
paperclip-mcp:
31+
build:
32+
context: .
33+
dockerfile: Dockerfile
34+
container_name: paperclip-mcp
35+
command: python server.py
36+
labels:
37+
- "traefik.enable=true"
38+
39+
# Define service first to avoid Traefik auto-generating conflicting services
40+
- "traefik.http.services.paperclip-mcp.loadbalancer.server.port=8000"
41+
42+
# MCP server route - accessible via HTTPS
43+
- "traefik.http.routers.paperclip-mcp.rule=Host(`paperclip.matsjfunke.com`)"
44+
- "traefik.http.routers.paperclip-mcp.entrypoints=websecure"
45+
- "traefik.http.routers.paperclip-mcp.tls.certresolver=myresolver"
46+
- "traefik.http.routers.paperclip-mcp.service=paperclip-mcp"
47+
48+
# CORS headers required for MCP protocol compatibility with AI clients
49+
- "traefik.http.middlewares.mcp-cors.headers.accesscontrolallowmethods=GET,POST,OPTIONS,PUT,DELETE"
50+
- "traefik.http.middlewares.mcp-cors.headers.accesscontrolallowheaders=Content-Type,Authorization,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since,mcp-session-id"
51+
- "traefik.http.middlewares.mcp-cors.headers.accesscontrolalloworiginlist=*"
52+
- "traefik.http.middlewares.mcp-cors.headers.accesscontrolmaxage=86400"
53+
54+
# Apply CORS middleware to the router
55+
- "traefik.http.routers.paperclip-mcp.middlewares=mcp-cors"
56+
57+
networks:
58+
- web
59+
restart: unless-stopped
60+
depends_on:
61+
- traefik
62+
environment:
63+
- PYTHONPATH=/app
64+
65+
volumes:
66+
# Named volume for Let's Encrypt certificates persistence across container restarts
67+
traefik_letsencrypt:
68+
69+
networks:
70+
# Internal network for container communication (external=false for security)
71+
web:
72+
external: false

docker-compose.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
version: "3.8"
2+
3+
services:
4+
paperclip:
5+
build:
6+
context: .
7+
image: paperclip-image
8+
container_name: paperclip
9+
ports:
10+
- 8000:8000
11+
volumes:
12+
- ./:/app # mount local backend dir to /app in container to enable live reloading of code changes
13+
command: watchmedo auto-restart --patterns="*.py" --recursive -- python src/server.py --transport http --host 0.0.0.0 --port 8000

0 commit comments

Comments
 (0)