CSCI 338: Fall 2025

Software Engineering

CSCI 338: Fall 2025

Project 2 > Cloud Deployment

Video Walkthroughs

  1. Introduction
  2. Code Modifications + Production Dockerfile
  3. Railway Deployment
  4. CI / CD in Action

1. Update Frontend API Configuration

Before creating the production Dockerfile, you need to update your App.jsx file to use an environment variable for the API URL. This allows your app to work in both development (localhost) and production (Railway).

Open ui/src/App.jsx and update the API URL configuration at the top of the file:

// Use environment variable for API URL, default to localhost for development
// In production (Railway), this will be empty string since frontend and backend are same origin
// Check if VITE_API_URL is explicitly set (even if empty string), otherwise use localhost
const API_URL =
    import.meta.env.VITE_API_URL !== undefined
        ? import.meta.env.VITE_API_URL      // address for production architecture
        : 'http://localhost:8000';          // address for local architecture

What this does:

Important: Make sure you’re using API_URL in all your fetch() calls throughout your React components. For example:

fetch(`${API_URL}/api/todos`)

Before you move on

Verify that:

  • Your App.jsx (or wherever you define your API URL) uses the environment variable
  • All your fetch() calls use the API_URL constant instead of hardcoded URLs

2. Create a Production Dockerfile

Create Dockerfile.prod in the root directory:

# Combined Production Dockerfile for Railway
# Builds both frontend and backend, serves them together

# Stage 1: Build Frontend
FROM node:20-slim as frontend-builder

WORKDIR /app/frontend

# Accept VITE_API_URL as build argument (defaults to empty string for production)
ARG VITE_API_URL=""
ENV VITE_API_URL=$VITE_API_URL

# Copy frontend package files
COPY ui/package.json ./

# Install dependencies
RUN npm install --production=false && \
    npm install @rollup/rollup-linux-x64-gnu --save-optional || true

# Copy frontend source code
COPY ui/ ./

# Build the React app
RUN npm run build

# Stage 2: Build Backend Dependencies
FROM python:3.11-slim as backend-builder

WORKDIR /app

# Install Poetry
RUN pip install --no-cache-dir poetry

# Configure Poetry
RUN poetry config virtualenvs.create false

# Copy backend dependency files
COPY backend/pyproject.toml backend/poetry.lock* ./

# Install dependencies (production only)
RUN poetry install --no-interaction --no-ansi --only=main --no-root

# Stage 3: Final Runtime Image
FROM python:3.11-slim

WORKDIR /app

# Copy installed Python packages and binaries from builder
COPY --from=backend-builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
COPY --from=backend-builder /usr/local/bin /usr/local/bin

# Copy backend application code
COPY backend/ ./backend/

# Copy built frontend from frontend-builder to /app/static
COPY --from=frontend-builder /app/frontend/dist ./static

# Create non-root user for security
RUN useradd -m -u 1000 appuser && \
    chown -R appuser:appuser /app

USER appuser

# Expose port (Railway sets PORT automatically)
EXPOSE 8080

# Use PORT environment variable if set, otherwise default to 8080
ENV PORT=8080

# Change to backend directory and run server
WORKDIR /app/backend
CMD sh -c "uvicorn server:app --host 0.0.0.0 --port \${PORT:-8080}"

What this Dockerfile does:

This Dockerfile uses a multi-stage build to create a single container that runs both your frontend and backend together. Here’s what happens in simple terms:

Stage 1: Build the Frontend

Stage 2: Prepare the Backend

Stage 3: Combine Everything

The result: One container that has everything your app needs to run in production. When Railway starts this container, it runs your FastAPI server, which handles both API requests and serves your React frontend.

Important Notes:

Before you move on

Verify your production Dockerfile is in the root directory.

3. Sign up for Railway

4. Create a new project

Within your new project, you will add two new containers (called services)

5. Create a PostgreSQL service

Once the service has been built, click on the PostgreSQL service you just created. Then:

Before you move on

Verify you have:

  • A PostgreSQL database created in Railway
  • The DATABASE_URL connection string copied
  • The connection string modified to use postgresql+asyncpg://

6. Create a Web Service

Now that you’ve created your database container, you’re ready to create your web server, which will host your Backend and your Frontend on the same box. To do this, you will add a second service to your project:

  1. Add your application service:
    • In your Railway project dashboard, click “+ New”
    • Select “GitHub Repo” → choose your repository
    • You will also need to teach Railway how to deploy your web server by going to Settings → Deploy → Dockerfile Path: Dockerfile.prod
  2. Set environment variables:
    • Go to the “Variables” tab in your application service
    • Add the DATABASE_URL variable:
      • Click “+ New Variable”
      • Name: DATABASE_URL
      • Value: Your modified connection string (with postgresql+asyncpg://)
      • Click “Add”
    • Important: Use the modified connection string (with +asyncpg) here
  3. Deploy:
    • Railway will automatically deploy when you push to your main branch
    • Or click “Deploy” in the dashboard to trigger a manual deployment
    • Watch the build logs to see the deployment progress
  4. Get your application URL:
    • Once deployed, Railway will provide a public URL
    • Go to Settings → Networking → Generate Domain
    • Your app will be available at: https://your-app-name.up.railway.app

Before you move on

Verify your application is:

  • Deployed on Railway
  • Connected to the PostgreSQL database
  • Accessible via a public URL
  • Test it by visiting your Railway URL

7. Verify Deployment

Test your deployed application:

  1. Visit your Railway URL:
    • Open https://your-app-name.up.railway.app in your browser
    • Your application should load
  2. Test API endpoints:
    • Visit https://your-app-name.up.railway.app/docs to see the FastAPI documentation
    • Test the /todos endpoint to verify database connectivity
  3. Test CRUD operations:
    • Create a new todo
    • Read todos
    • Update a todo
    • Delete a todo
    • Verify all operations work correctly

Before you move on

Verify your deployed application:

  • Loads correctly in the browser
  • Has working API endpoints
  • Successfully performs all CRUD operations
  • Is accessible from any device with internet access

← Back to Project 2 Instructions