DevOps and Deployment
Read this when you set up CI or deploy for the first time, ideally in fall; it gives you the pipeline shape, the environments, and a rollback plan.
DevOps is the set of practices that bridges writing code and running it in production. Deployment is the act of getting your software from a repository to a place where users can actually use it. Without a deliberate approach to both:
- Deployments happen manually and inconsistently, with each team member doing it slightly differently.
- Bugs that the team caught locally slip through because the production environment behaves differently.
- A deployment breaks the live system, and the team has no way to roll back quickly.
- Setting up a development environment takes hours of tribal knowledge instead of a single command.
The goal is not to become infrastructure experts. It is to reach a state where deploying is boring: predictable, repeatable, and recoverable when something goes wrong.
Development Environment Setup
Section titled “Development Environment Setup”Before the team can deploy anything, every member needs to be able to run the project locally. This sounds trivial, but “it works on my machine” is one of the most common sources of wasted time in team projects.
A good development setup has three properties:
- Documented. A new team member (or an AI coding tool) can go from cloning the repository to running the application by following a README. No Slack messages, no “ask Jordan, they know how to set it up.”
- Reproducible. Every team member runs the same versions of dependencies, the same database schema, and the same configuration. Differences between environments are a source of bugs that are painful to diagnose.
- Fast. Getting started should take minutes, not hours. If setup requires installing six tools and configuring three services manually, automate it.
The README as Setup Guide
Section titled “The README as Setup Guide”Your repository’s README should include a “Getting Started” section that covers:
## Getting Started
### Prerequisites- Node.js 24+ (recommend using [nvm](https://github.com/nvm-sh/nvm))- PostgreSQL 16+- A `.env` file (copy `.env.example` and fill in values)
### Setupgit clone https://github.com/your-team/your-project.gitcd your-projectcp .env.example .env # then edit with your local valuesnpm installnpm run db:migratenpm run dev # starts the dev server at localhost:3000If the project has multiple services (a frontend, a backend, a database), document how to start each one and in what order.
Containers for Consistency
Section titled “Containers for Consistency”If the project has complex dependencies (a specific database version, a message queue, system-level libraries), Docker and Docker Compose can standardize the environment across the team:
services: app: build: . ports: - "3000:3000" environment: - DATABASE_URL=postgres://user:password@db:5432/mydb depends_on: - db db: image: postgres:16 environment: - POSTGRES_USER=user - POSTGRES_PASSWORD=password - POSTGRES_DB=mydb volumes: - pgdata:/var/lib/postgresql/data
volumes: pgdata:With this file, docker compose up starts the entire stack regardless of what is installed on each team member’s machine. This is especially valuable when the team uses different operating systems.
Containers are not required for every project. A simple frontend application or a Python script may not need them. Use containers when environment differences are causing problems or when the project has dependencies that are difficult to install consistently.
Continuous Integration (CI)
Section titled “Continuous Integration (CI)”Continuous integration means automatically running checks (tests, linting, type checking, builds) on every push and pull request. CI catches problems before they reach the main branch, which means before they affect the rest of the team.
GitHub Actions
Section titled “GitHub Actions”GitHub Actions is the most common CI platform for GitHub-hosted projects. It is free for public repositories and has generous limits for private ones. A basic workflow file lives in .github/workflows/:
name: CIon: push: branches: [main] pull_request: branches: [main]
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7
- name: Set up Node.js uses: actions/setup-node@v7 with: node-version: 24 cache: npm
- name: Install dependencies run: npm ci
- name: Lint run: npm run lint
- name: Type check run: npm run typecheck
- name: Run tests run: npm testThis workflow runs on every push to main and on every pull request targeting main. If any step fails, the workflow fails, and the PR is marked accordingly.
For Python projects, the setup is similar:
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7
- name: Set up Python uses: actions/setup-python@v7 with: python-version: "3.13"
- name: Install dependencies run: pip install -r requirements.txt
- name: Lint run: ruff check .
- name: Run tests run: pytestWhat to Run in CI
Section titled “What to Run in CI”At minimum, CI should run:
- Tests. The automated test suite is the most important check. If tests pass locally but fail in CI, that is a signal that the test depends on local state, which is a bug in the test.
- Linting. Enforces code style consistency without relying on everyone to remember to run the linter.
- Build. Verifies that the project compiles and builds successfully. A PR that passes tests but breaks the build is still broken.
Optional but valuable:
- Type checking (TypeScript’s
tsc, Python’smypyorpyright). Catches type errors before runtime. - Security audits (
npm audit,pip-audit). Flags known vulnerabilities in dependencies. - Preview deployments. Some platforms (Vercel, Netlify) automatically deploy every PR to a unique URL so reviewers can see the changes live.
CI and Branch Protection
Section titled “CI and Branch Protection”CI is most effective when combined with branch protection rules that require checks to pass before merging. That turns CI from an informational signal (“the build failed”) into an enforced gate (“you cannot merge until the build passes”), and it is the single most effective way to keep the main branch stable. The settings are in Git and GitHub: Protecting the Main Branch.
Continuous Deployment (CD)
Section titled “Continuous Deployment (CD)”Continuous deployment means automatically deploying the application when code is merged to main. For many Capstone projects, this is the right approach: the main branch is always in a deployable state (because CI enforces that), so deploying it automatically is a natural extension.
Platform-as-a-Service (PaaS)
Section titled “Platform-as-a-Service (PaaS)”For most Capstone projects, a platform-as-a-service provider is the right deployment target. These platforms handle server provisioning, HTTPS, scaling, and most infrastructure concerns so the team can focus on the application:
| Platform | Good For | Free Tier |
|---|---|---|
| Vercel | Frontend apps, Next.js, static sites | Yes |
| Netlify | Static sites, serverless functions | Yes |
| Railway | Full-stack apps, databases, background jobs | Trial credits |
| Render | Web services, databases, cron jobs | Yes (limited) |
| Fly.io | Docker-based apps, globally distributed | Yes (limited) |
| Heroku | Traditional web apps, add-on ecosystem | No (paid only) |
| GitHub Pages | Static sites, documentation | Yes |
Most of these platforms support automatic deployments from GitHub: connect the repository, specify the build command and output directory, and every push to main triggers a deployment.
Deploying to a VM or VPS
Section titled “Deploying to a VM or VPS”Some projects need more control than a PaaS provides: custom system packages, GPU access, specific network configurations, or long-running background processes. In these cases, deploying to a virtual machine (DigitalOcean Droplet, AWS EC2, Google Compute Engine, OSU infrastructure) may be appropriate.
Deploying to a VM involves more operational responsibility:
- SSH access and security. Use SSH keys, not passwords. Disable root login. Keep the OS updated.
- Process management. Use
systemd,pm2, orsupervisorto keep your application running and restart it if it crashes. - Reverse proxy. Use Nginx or Caddy in front of your application to handle HTTPS, static files, and request routing.
- Updates. You are responsible for deploying new versions. Automate this with a GitHub Actions workflow that SSHs into the server and pulls the latest code, or use a tool like Ansible or Kamal.
A VM deployment is more work to set up and maintain, but it teaches skills that are directly transferable to industry roles.
Database Hosting
Section titled “Database Hosting”If your project uses a database, you need somewhere to host it in production. Options:
- Managed database services: Supabase (PostgreSQL), Neon (serverless PostgreSQL), PlanetScale (MySQL and PostgreSQL), MongoDB Atlas (MongoDB), Turso (SQLite), Upstash (Redis). These handle backups, scaling, and security patches.
- Platform-provided databases: Railway, Render, Fly.io, and Heroku offer database add-ons that integrate with the application deployment.
- Cloud provider services: AWS RDS, Google Cloud SQL, and Azure Database if your partner already runs on that cloud, which is often the deciding factor.
- Self-hosted on a VM: Running PostgreSQL or MySQL on your own server gives full control but requires you to handle backups, updates, and security.
For most Capstone projects, a managed service is the right choice: it removes operational burden you have no time for. Check the current pricing page before you commit. Free and hobby tiers appear and disappear on a timescale shorter than this course, so confirm what you are actually getting rather than trusting a recommendation from last year. If your partner already runs on a particular cloud, use theirs; matching the partner’s stack is worth more than picking the nicest developer experience.
Environment Management
Section titled “Environment Management”Most applications behave differently in development, staging, and production. Environment variables control these differences without changing code.
The Three Environments
Section titled “The Three Environments”- Development (local). Runs on each developer’s machine. Uses local databases, mock services, verbose logging, and debug tools.
- Staging (optional). A deployed version that mirrors production but is not public. Useful for testing deployments and integrations before they reach real users.
- Production. The live system that users interact with. Uses real databases, real API keys, minimal logging, and error tracking.
Environment Variables
Section titled “Environment Variables”Configuration that changes between environments (database URLs, API keys, feature flags, log levels) should be stored in environment variables, not in code:
// Good: reads from the environmentconst dbUrl = process.env.DATABASE_URL;
// Bad: hardcoded connection stringconst dbUrl = "postgres://user:password@localhost:5432/mydb";Each environment has its own set of variables:
- Local: defined in
.env(never committed). - CI: defined in GitHub Actions workflow files or repository secrets.
- Production: defined in the hosting platform’s settings (Vercel Environment Variables, Railway Variables, Heroku Config Vars).
Database Migrations
Section titled “Database Migrations”Changes to the database schema should be managed through migration files, not manual SQL commands. Migrations are version-controlled scripts that transform the database from one state to the next:
# Create a new migrationnpx prisma migrate dev --name add-user-roles
# Apply migrations in productionnpx prisma migrate deployEvery ORM and database toolkit has a migration system: Prisma (JavaScript/TypeScript), Alembic (Python/SQLAlchemy), Django migrations (Python), Flyway (Java), golang-migrate (Go). Use whichever matches your stack.
The key discipline is: never modify the database schema by hand in production. Always create a migration, test it locally, commit it, and apply it through the deployment pipeline. This ensures every environment has the same schema and that changes are reversible.
Deployment Checklist
Section titled “Deployment Checklist”Before your first production deployment (and periodically after), walk through this checklist:
Application
Section titled “Application”- The application builds and starts successfully with production configuration.
- Environment variables are set for production (database URL, API keys, secrets).
- Debug mode is off and error pages do not expose stack traces.
- Logging is configured to capture errors without leaking sensitive data.
Infrastructure
Section titled “Infrastructure”- HTTPS is enabled (most PaaS providers handle this automatically).
- The database is hosted and accessible from the production environment.
- Database migrations have been run against the production database.
- DNS is configured if using a custom domain.
Operations
Section titled “Operations”- The team knows how to trigger a deployment (automatic on merge, or manual steps).
- The team knows how to roll back a deployment if something goes wrong.
- Error tracking is set up (Sentry, LogRocket, or at minimum, centralized logging).
- The team has access to production logs for debugging.
Security
Section titled “Security”- Secrets are stored in the platform’s environment variable system, not in code.
- Default credentials have been changed.
- CORS is configured appropriately.
- The
.gitignoreincludes.envand other sensitive files.
Monitoring and Error Tracking
Section titled “Monitoring and Error Tracking”Deploying the application is not the end. You need to know when something breaks in production, ideally before a user reports it.
Error tracking services like Sentry (free tier available) capture unhandled exceptions with full stack traces, request context, and the release version that introduced the error. Setting up Sentry takes a few minutes and provides visibility that console logging cannot match.
At minimum, ensure the team has a way to:
- See errors that occur in production.
- Identify which deployment introduced a regression.
- Access application logs from the hosting platform.
You do not need a sophisticated monitoring stack. For a Capstone project, Sentry plus the hosting platform’s built-in logs is sufficient. The important thing is that errors do not disappear silently.
Rollbacks
Section titled “Rollbacks”Every deployment should be reversible. When a deployment breaks something, the team needs to restore the previous working version quickly. How you roll back depends on your deployment approach:
- PaaS platforms: Vercel, Netlify, and Render allow instant rollbacks to any previous deployment through their dashboard. Learn where this button is before you need it.
- Container-based deployments: Keep the previous container image tagged and ready. Rolling back means deploying the previous image.
- VM deployments: If deploying via
git pull, a rollback means checking out the previous commit and restarting the service. Automate this so it is a single command, not a scramble.
A deployment process without a rollback plan is incomplete. The first time a deploy goes wrong at 10 PM before a demo, you will be glad you thought about this in advance.
Best Practices
Section titled “Best Practices”- Automate everything you do more than twice. If you SSH into a server and run the same three commands on every deploy, put them in a script or a GitHub Actions workflow.
- Deploy early and often. The first deployment should happen in the first or second sprint, even if the application does little. Deploying a “Hello World” page validates the entire pipeline. Waiting until the project is “ready” means debugging application bugs and infrastructure bugs simultaneously.
- Keep the main branch deployable at all times. CI plus branch protection makes this achievable. If
mainis always in a working state, deploying from it is safe. - Use the same build process everywhere. If the application builds with
npm run buildlocally, it should build withnpm run buildin CI and in production. Different build commands in different environments hide bugs. - Treat infrastructure configuration as code. Docker Compose files, GitHub Actions workflows, Nginx configs, and deployment scripts belong in the repository and go through the same review process as application code.
- Document your deployment process. Even if it is automated, write down what happens when, how to trigger a deploy, and how to roll back. The team member who set it up will not always be available when something goes wrong.
Some Truths About DevOps
Section titled “Some Truths About DevOps”- The first deployment is the hardest. Every one after that is routine, provided you automate it.
- Teams that delay deployment until “the code is ready” inevitably discover deployment problems at the worst possible time: right before a demo or handoff.
- A deployment that takes 30 minutes of manual steps will eventually be done incorrectly. Automation is not a luxury. It is how you prevent mistakes.
- Most production outages in student projects are caused by configuration errors (wrong environment variable, missing database migration, debug mode on), not by code bugs. The deployment checklist exists for this reason.
- You do not need Kubernetes. For a Capstone project, a PaaS with automatic deployments is almost certainly sufficient. Choose the simplest infrastructure that meets your needs.
- Containers are powerful but optional. If
npm install && npm startworks reliably for your team, you do not need Docker. Add it when environment inconsistency becomes a real problem, not as a resume-driven decision.
DevOps in Industry and Academia
Section titled “DevOps in Industry and Academia”In industry, DevOps is not a separate role for most organizations. It is a set of practices that every software engineer is expected to understand. CI/CD pipelines, containerized deployments, infrastructure as code, and monitoring are baseline skills for software engineering positions. Companies like Google, Netflix, and Amazon deploy hundreds or thousands of times per day, and their ability to do so reliably depends on the automation and practices described in this guide.
The “you build it, you run it” philosophy (attributed to Amazon’s Werner Vogels) means that the team that writes the code is also responsible for deploying, monitoring, and supporting it. Capstone projects are a natural place to practice this: your team builds the software, and your team is responsible for getting it running and keeping it running.
In research settings, reproducibility is the equivalent of reliable deployment. A research project that cannot be set up and run by someone other than the original author has limited scientific value. Docker, dependency pinning, and clear setup instructions serve the same purpose in academia as deployment pipelines serve in industry: they make the work reproducible by others.
The deployment skills you develop during Capstone transfer directly. Every professional software project needs to go from code to running system, and the team that understands that pipeline has a significant advantage over one that treats deployment as someone else’s problem.