Skip to content

Commit 1764087

Browse files
Merge pull request #91 from festim-dev/deploy-google-cloud
Deploy google cloud
2 parents 8c8be40 + da9143c commit 1764087

File tree

4 files changed

+137
-11
lines changed

4 files changed

+137
-11
lines changed

Dockerfile

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Multi-stage build optimized for Google Cloud Run
2+
FROM node:18-alpine AS frontend-build
3+
4+
# Build frontend
5+
WORKDIR /app
6+
COPY package*.json ./
7+
RUN npm ci
8+
COPY . .
9+
RUN npm run build
10+
11+
# Python backend stage
12+
FROM python:3.11-slim
13+
14+
# Install system dependencies
15+
RUN apt-get update && apt-get install -y \
16+
gcc \
17+
&& rm -rf /var/lib/apt/lists/*
18+
19+
# Set working directory
20+
WORKDIR /app
21+
22+
# Copy Python requirements and install
23+
COPY requirements.txt .
24+
RUN pip install --no-cache-dir -r requirements.txt
25+
26+
# Install gunicorn for production WSGI server
27+
RUN pip install gunicorn
28+
29+
# Copy backend source code
30+
COPY src/ ./src/
31+
COPY *.py ./
32+
33+
# Copy built frontend from previous stage
34+
COPY --from=frontend-build /app/dist ./dist
35+
36+
# Create necessary directories
37+
RUN mkdir -p saved_graphs plots
38+
39+
# Create non-root user for security
40+
RUN useradd --create-home --shell /bin/bash app \
41+
&& chown -R app:app /app
42+
USER app
43+
44+
# Set environment variables
45+
ENV FLASK_APP=src.backend
46+
ENV FLASK_ENV=production
47+
ENV PYTHONPATH=/app
48+
ENV PORT=8080
49+
50+
# Expose port (Cloud Run uses PORT env variable)
51+
EXPOSE $PORT
52+
53+
# Health check
54+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
55+
CMD curl -f http://localhost:$PORT/health || exit 1
56+
57+
# Use gunicorn for production
58+
CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 src.backend:app

src/App.jsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
import '@xyflow/react/dist/style.css';
1212
import './App.css';
1313
import Plot from 'react-plotly.js';
14+
import { getApiEndpoint } from './config.js';
1415

1516
import ContextMenu from './ContextMenu.jsx';
1617

@@ -347,7 +348,7 @@ export default function App() {
347348
globalVariables
348349
};
349350

350-
const response = await fetch('http://localhost:8000/convert-to-python', {
351+
const response = await fetch(getApiEndpoint('/convert-to-python'), {
351352
method: 'POST',
352353
headers: {
353354
'Content-Type': 'application/json',
@@ -417,7 +418,7 @@ export default function App() {
417418
globalVariables
418419
};
419420

420-
const response = await fetch('http://localhost:8000/run-pathsim', {
421+
const response = await fetch(getApiEndpoint('/run-pathsim'), {
421422
method: 'POST',
422423
headers: {
423424
'Content-Type': 'application/json',

src/backend.py

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,49 @@
1212
from .pathsim_utils import make_pathsim_model
1313
from pathsim.blocks import Scope
1414

15-
app = Flask(__name__)
16-
CORS(
17-
app,
18-
resources={r"/*": {"origins": "http://localhost:5173"}},
19-
supports_credentials=True,
20-
)
15+
# Configure Flask app for Cloud Run
16+
app = Flask(__name__, static_folder="../dist", static_url_path="")
17+
18+
# Configure CORS based on environment
19+
if os.getenv("FLASK_ENV") == "production":
20+
# Production: Allow Cloud Run domains and common domains
21+
CORS(
22+
app,
23+
resources={
24+
r"/*": {
25+
"origins": ["*"], # Allow all origins for Cloud Run
26+
"methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
27+
"allow_headers": ["Content-Type", "Authorization"],
28+
}
29+
},
30+
)
31+
else:
32+
# Development: Only allow localhost
33+
CORS(
34+
app,
35+
resources={
36+
r"/*": {"origins": ["http://localhost:5173", "http://localhost:3000"]}
37+
},
38+
supports_credentials=True,
39+
)
2140

2241

2342
# Creates directory for saved graphs
2443
SAVE_DIR = "saved_graphs"
2544
os.makedirs(SAVE_DIR, exist_ok=True)
2645

2746

28-
# Health check endpoint for CI/CD
29-
@app.route("/", methods=["GET"])
47+
# Serve React frontend for production
48+
@app.route("/")
49+
def serve_frontend():
50+
"""Serve the React frontend in production."""
51+
if os.getenv("FLASK_ENV") == "production":
52+
return app.send_static_file("index.html")
53+
else:
54+
return jsonify({"message": "Fuel Cycle Simulator API", "status": "running"})
55+
56+
57+
# Health check endpoint for Cloud Run
3058
@app.route("/health", methods=["GET"])
3159
def health_check():
3260
return jsonify(
@@ -184,5 +212,16 @@ def run_pathsim():
184212
return jsonify({"success": False, "error": f"Server error: {str(e)}"}), 500
185213

186214

215+
# Catch-all route for React Router (SPA routing)
216+
@app.route("/<path:path>")
217+
def catch_all(path):
218+
"""Serve React app for all routes in production (for client-side routing)."""
219+
if os.getenv("FLASK_ENV") == "production":
220+
return app.send_static_file("index.html")
221+
else:
222+
return jsonify({"error": "Route not found"}), 404
223+
224+
187225
if __name__ == "__main__":
188-
app.run(port=8000, debug=True)
226+
port = int(os.getenv("PORT", 8000))
227+
app.run(host="0.0.0.0", port=port, debug=os.getenv("FLASK_ENV") != "production")

src/config.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// API configuration for development and production
2+
const API_CONFIG = {
3+
development: {
4+
baseUrl: 'http://localhost:8000'
5+
},
6+
production: {
7+
baseUrl: '' // Use relative URLs in production (same domain)
8+
}
9+
};
10+
11+
// Get the current environment
12+
const getCurrentEnvironment = () => {
13+
return process.env.NODE_ENV === 'production' ? 'production' : 'development';
14+
};
15+
16+
// Get the API base URL for the current environment
17+
export const getApiUrl = () => {
18+
const env = getCurrentEnvironment();
19+
return API_CONFIG[env].baseUrl;
20+
};
21+
22+
// Helper function to construct full API endpoint URLs
23+
export const getApiEndpoint = (endpoint) => {
24+
const baseUrl = getApiUrl();
25+
return `${baseUrl}${endpoint}`;
26+
};
27+
28+
export default API_CONFIG;

0 commit comments

Comments
 (0)