Skip to content

Commit 5bbc5a9

Browse files
authored
🐳 Create and publish Docker images for API and CatalogSync (#83)
This change adds Dockerfile definitions and a workflow for verifying and publishing docker images for Api and CatalogSync projects.
1 parent c96c995 commit 5bbc5a9

11 files changed

Lines changed: 1215 additions & 2 deletions

.dockerignore

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Build artifacts
2+
**/bin/
3+
**/obj/
4+
**/publish/
5+
**/.vs/
6+
**/.vscode/
7+
8+
# Git
9+
.git/
10+
.gitignore
11+
.gitattributes
12+
.github/
13+
14+
# Documentation
15+
*.md
16+
!src/**/*.md
17+
docs/
18+
19+
# Database files
20+
**/*.sqlite
21+
**/*.sqlite-shm
22+
**/*.sqlite-wal
23+
**/*.mdf
24+
**/*.ldf
25+
26+
# Node/Frontend
27+
node_modules/
28+
**/node_modules/
29+
30+
# IDE
31+
.vs/
32+
.vscode/
33+
*.suo
34+
*.user
35+
*.userosscache
36+
*.sln.docstates
37+
38+
# Tests
39+
**/Tests/
40+
41+
# Docker
42+
Dockerfile*
43+
docker-compose*.yml
44+
.dockerignore
45+
.env
46+
.env.*
47+
48+
# CI/CD
49+
.github/
50+
51+
# Claude
52+
.claude/
53+
54+
# Misc
55+
*.log
56+
*.tmp

.env.example

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# ===================================================================
2+
# Purdue.io Docker Configuration
3+
# ===================================================================
4+
# Copy this file to .env and update with your configuration
5+
#
6+
# IMPORTANT: Never commit .env file to version control!
7+
# ===================================================================
8+
9+
# -------------------------------------------------------------------
10+
# PostgreSQL Database Configuration
11+
# -------------------------------------------------------------------
12+
POSTGRES_DB=purdueio
13+
POSTGRES_USER=purdueio
14+
POSTGRES_PASSWORD=changeme_in_production
15+
16+
# -------------------------------------------------------------------
17+
# API Configuration
18+
# -------------------------------------------------------------------
19+
# Port to expose the API on the host machine
20+
API_PORT=8080
21+
22+
# ASP.NET Core environment (Development or Production)
23+
ASPNETCORE_ENVIRONMENT=Production
24+
25+
# -------------------------------------------------------------------
26+
# CatalogSync Configuration
27+
# -------------------------------------------------------------------
28+
29+
# Sync Schedule (cron expression)
30+
# Determines when the catalog sync runs automatically
31+
#
32+
# Default: 0 2 * * * (Daily at 2:00 AM)
33+
#
34+
# Common Examples:
35+
# - "0 2 * * *" = Daily at 2:00 AM (DEFAULT)
36+
# - "0 */6 * * *" = Every 6 hours
37+
# - "0 */2 * * *" = Every 2 hours
38+
# - "*/30 * * * *" = Every 30 minutes
39+
# - "0 0 * * 0" = Weekly on Sunday at midnight
40+
#
41+
SYNC_SCHEDULE=0 2 * * *
42+
43+
# Terms to Sync (optional)
44+
# Comma-separated list of term codes to sync
45+
# Example: SYNC_TERMS=202410,202510,202520
46+
# Leave empty to sync all available terms based on SYNC_ALL_TERMS setting
47+
SYNC_TERMS=
48+
49+
# Subjects to Sync (optional)
50+
# Comma-separated list of subject codes to sync
51+
# Example: SYNC_SUBJECTS=CS,MA,PHYS,ECE,ENGL
52+
# Leave empty to sync all subjects
53+
SYNC_SUBJECTS=
54+
55+
# Sync All Terms
56+
# Controls whether to sync all historical terms or only current/future terms
57+
# - false (default): Only sync current and future terms
58+
# - true: Sync all available terms including historical data
59+
SYNC_ALL_TERMS=false
60+
61+
# Run Once Mode (for testing/initialization)
62+
# Set to "true" to run the sync once and exit (useful for testing)
63+
# Set to "false" for continuous cron scheduling (production mode)
64+
RUN_ONCE=false
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
name: Docker Build and Publish
2+
3+
on:
4+
pull_request:
5+
branches: [ main ]
6+
paths:
7+
- 'src/**'
8+
- 'Dockerfile.*'
9+
- 'docker/**'
10+
- '.github/workflows/docker-publish.yml'
11+
release:
12+
types: [published]
13+
workflow_dispatch:
14+
inputs:
15+
tag:
16+
description: 'Tag to build and push (e.g., v1.0.0 or latest)'
17+
required: true
18+
default: 'latest'
19+
20+
env:
21+
REGISTRY: ghcr.io
22+
IMAGE_NAME_API: ${{ github.repository }}/api
23+
IMAGE_NAME_CATALOGSYNC: ${{ github.repository }}/catalogsync
24+
25+
jobs:
26+
# Verify builds on PRs (no push)
27+
verify-build:
28+
name: Verify Docker Builds
29+
runs-on: ubuntu-latest
30+
if: github.event_name == 'pull_request'
31+
32+
strategy:
33+
matrix:
34+
service: [api, catalogsync]
35+
36+
steps:
37+
- name: Checkout code
38+
uses: actions/checkout@v4
39+
40+
- name: Set up Docker Buildx
41+
uses: docker/setup-buildx-action@v3
42+
43+
- name: Build ${{ matrix.service }} (verification only)
44+
uses: docker/build-push-action@v5
45+
with:
46+
context: .
47+
file: ./Dockerfile.${{ matrix.service }}
48+
push: false
49+
tags: purdueio-${{ matrix.service }}:test
50+
cache-from: type=gha
51+
cache-to: type=gha,mode=max
52+
53+
# Build and push on releases
54+
build-and-push:
55+
name: Build and Push Docker Images
56+
runs-on: ubuntu-latest
57+
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
58+
permissions:
59+
contents: read
60+
packages: write
61+
62+
strategy:
63+
matrix:
64+
include:
65+
- service: api
66+
image_name_var: IMAGE_NAME_API
67+
- service: catalogsync
68+
image_name_var: IMAGE_NAME_CATALOGSYNC
69+
70+
steps:
71+
- name: Checkout code
72+
uses: actions/checkout@v4
73+
74+
- name: Set up Docker Buildx
75+
uses: docker/setup-buildx-action@v3
76+
77+
- name: Log in to Container Registry
78+
uses: docker/login-action@v3
79+
with:
80+
registry: ${{ env.REGISTRY }}
81+
username: ${{ github.actor }}
82+
password: ${{ secrets.GITHUB_TOKEN }}
83+
84+
- name: Extract metadata (tags, labels) for Docker
85+
id: meta
86+
uses: docker/metadata-action@v5
87+
with:
88+
images: ${{ env.REGISTRY }}/${{ matrix.service == 'api' && env.IMAGE_NAME_API || env.IMAGE_NAME_CATALOGSYNC }}
89+
tags: |
90+
type=semver,pattern={{version}}
91+
type=semver,pattern={{major}}.{{minor}}
92+
type=semver,pattern={{major}}
93+
type=raw,value=latest,enable={{is_default_branch}}
94+
type=raw,value=${{ github.event.inputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }}
95+
96+
- name: Build and push ${{ matrix.service }}
97+
uses: docker/build-push-action@v5
98+
with:
99+
context: .
100+
file: ./Dockerfile.${{ matrix.service }}
101+
push: true
102+
tags: ${{ steps.meta.outputs.tags }}
103+
labels: ${{ steps.meta.outputs.labels }}
104+
cache-from: type=gha
105+
cache-to: type=gha,mode=max
106+
107+
- name: Output image tags
108+
run: |
109+
echo "### Published ${{ matrix.service }} image :rocket:" >> $GITHUB_STEP_SUMMARY
110+
echo "" >> $GITHUB_STEP_SUMMARY
111+
echo "**Tags:**" >> $GITHUB_STEP_SUMMARY
112+
echo '```' >> $GITHUB_STEP_SUMMARY
113+
echo "${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY
114+
echo '```' >> $GITHUB_STEP_SUMMARY

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,13 @@ $RECYCLE.BIN/
194194
.DS_Store
195195
*.mdf
196196
*.ldf
197+
198+
# =========================
199+
# Docker
200+
# =========================
201+
202+
# Environment files (contain secrets)
203+
.env
204+
205+
# Docker temporary files
206+
.docker/

Dockerfile.api

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Stage 1: Build
2+
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
3+
WORKDIR /src
4+
5+
# Copy only the project files needed for API (and its dependencies)
6+
COPY src/Api/Api.csproj ./Api/
7+
COPY src/Database/Database.csproj ./Database/
8+
COPY src/Database.Migrations.Sqlite/Database.Migrations.Sqlite.csproj ./Database.Migrations.Sqlite/
9+
COPY src/Database.Migrations.Npgsql/Database.Migrations.Npgsql.csproj ./Database.Migrations.Npgsql/
10+
11+
# Restore dependencies for API project only (not entire solution)
12+
RUN dotnet restore Api/Api.csproj
13+
14+
# Copy all source files
15+
COPY src/ ./
16+
17+
# Publish the API project
18+
RUN dotnet publish Api/Api.csproj -c Release -r linux-x64 \
19+
--self-contained=true \
20+
-p:PublishReadyToRun=true \
21+
-o /app/publish
22+
23+
# Stage 2: Runtime
24+
FROM mcr.microsoft.com/dotnet/aspnet:9.0
25+
WORKDIR /app
26+
27+
# Copy published application (includes wwwroot if present)
28+
COPY --from=build /app/publish .
29+
30+
# Expose port 8080 (HTTP only for internal Docker network)
31+
EXPOSE 8080
32+
33+
# Configure ASP.NET Core to listen on port 8080
34+
ENV ASPNETCORE_URLS=http://+:8080 \
35+
ASPNETCORE_ENVIRONMENT=Production
36+
37+
# Run as non-root user for security
38+
USER app
39+
40+
ENTRYPOINT ["./Api"]

Dockerfile.catalogsync

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Stage 1: Build
2+
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
3+
WORKDIR /src
4+
5+
# Copy only the project files needed for CatalogSync (and its dependencies)
6+
COPY src/CatalogSync/CatalogSync.csproj ./CatalogSync/
7+
COPY src/Database/Database.csproj ./Database/
8+
COPY src/Scraper/Scraper.csproj ./Scraper/
9+
COPY src/Database.Migrations.Sqlite/Database.Migrations.Sqlite.csproj ./Database.Migrations.Sqlite/
10+
COPY src/Database.Migrations.Npgsql/Database.Migrations.Npgsql.csproj ./Database.Migrations.Npgsql/
11+
12+
# Restore dependencies for CatalogSync project only (not entire solution)
13+
RUN dotnet restore CatalogSync/CatalogSync.csproj
14+
15+
# Copy all source files
16+
COPY src/ ./
17+
18+
# Publish the CatalogSync project
19+
RUN dotnet publish CatalogSync/CatalogSync.csproj -c Release -r linux-x64 \
20+
--self-contained=true \
21+
-p:PublishReadyToRun=true \
22+
-o /app/publish
23+
24+
# Stage 2: Runtime with supercronic
25+
FROM mcr.microsoft.com/dotnet/aspnet:9.0
26+
WORKDIR /app
27+
28+
# Install supercronic for Docker-friendly cron scheduling
29+
ENV SUPERCRONIC_URL=https://github.com/aptible/supercronic/releases/download/v0.2.29/supercronic-linux-amd64 \
30+
SUPERCRONIC=supercronic-linux-amd64 \
31+
SUPERCRONIC_SHA1SUM=cd48d45c4b10f3f0bfdd3a57d054cd05ac96812b
32+
33+
RUN apt-get update && apt-get install -y --no-install-recommends curl \
34+
&& curl -fsSLO "$SUPERCRONIC_URL" \
35+
&& echo "${SUPERCRONIC_SHA1SUM} ${SUPERCRONIC}" | sha1sum -c - \
36+
&& chmod +x "$SUPERCRONIC" \
37+
&& mv "$SUPERCRONIC" /usr/local/bin/supercronic \
38+
&& apt-get purge -y --auto-remove curl \
39+
&& rm -rf /var/lib/apt/lists/*
40+
41+
# Copy published application
42+
COPY --from=build /app/publish .
43+
44+
# Copy entrypoint script and ensure it's executable by all users
45+
COPY docker/catalogsync-entrypoint.sh /app/entrypoint.sh
46+
RUN chmod 755 /app/entrypoint.sh
47+
48+
# Run as non-root user for security
49+
USER app
50+
51+
ENTRYPOINT ["/app/entrypoint.sh"]

README.md

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,47 @@ there through the query tester at [http://api.purdue.io/](api.purdue.io/).
5252

5353
# Building and Running
5454

55-
## Tools
55+
## Docker (Recommended)
5656

57-
Purdue.io is written in C# on .NET 8. It will run natively on most major
57+
The easiest way to run Purdue.io is using Docker. This will start all services including PostgreSQL, the API, and the CatalogSync scheduler.
58+
59+
### Prerequisites
60+
61+
- [Docker](https://docs.docker.com/get-docker/)
62+
- [Docker Compose](https://docs.docker.com/compose/install/)
63+
64+
### Quick Start (Using Pre-built Images)
65+
66+
```sh
67+
# 1. Download the production docker-compose file
68+
curl -O https://raw.githubusercontent.com/Purdue-io/PurdueApi/main/docker-compose.production.yml
69+
curl -O https://raw.githubusercontent.com/Purdue-io/PurdueApi/main/.env.example
70+
71+
# 2. Copy the example environment file
72+
cp .env.example .env
73+
74+
# 3. (Optional) Edit .env to configure sync schedule, database credentials, etc.
75+
76+
# 4. Pull and start all services
77+
docker compose -f docker-compose.production.yml up -d
78+
79+
# 5. Watch the logs
80+
docker compose -f docker-compose.production.yml logs -f
81+
82+
# 6. Access the API at http://localhost:8080/odata
83+
```
84+
85+
**Pre-built images are available at:**
86+
- `ghcr.io/purdue-io/purdueapi/api:latest`
87+
- `ghcr.io/purdue-io/purdueapi/catalogsync:latest`
88+
89+
For building from source, detailed configuration options, and troubleshooting, see [docs/DOCKER.md](docs/DOCKER.md).
90+
91+
## Local Development
92+
93+
### Tools
94+
95+
Purdue.io is written in C# on .NET 9. It will run natively on most major
5896
architectures and operating systems (Windows, Linux, Mac OS).
5997

6098
Entity Framework is used to communicate with an underlying database provider. Currently,

0 commit comments

Comments
 (0)