From dd6783c23910b61f7ed7a7fc727b135a6cce0253 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Nov 2025 23:03:15 +0000 Subject: [PATCH 1/4] Initial plan From 84352ba404a90dc6a611d3fd9733c405cf43502e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Nov 2025 23:09:23 +0000 Subject: [PATCH 2/4] Add Docker support with automated GHCR publishing Co-authored-by: abeckDev <8720854+abeckDev@users.noreply.github.com> --- .dockerignore | 47 +++++++++++++ .github/workflows/docker-publish.yml | 67 ++++++++++++++++++ Dockerfile | 54 ++++++++++++++ README.md | 101 +++++++++++++++++++++++++++ 4 files changed, 269 insertions(+) create mode 100644 .dockerignore create mode 100644 .github/workflows/docker-publish.yml create mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ff58e11 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,47 @@ +# .NET build artifacts +**/bin/ +**/obj/ +**/out/ +**/TestResults/ + +# Visual Studio / VS Code +.vs/ +.vscode/ +*.user +*.suo +*.userprefs + +# Git +.git/ +.gitignore +.gitattributes + +# Docker +Dockerfile +.dockerignore + +# Documentation +*.md +LICENSE + +# CI/CD +.github/ + +# Development containers +.devcontainer/ + +# Environment files +*.env +appsettings.Development.json + +# Logs +*.log + +# Test artifacts +coverage*.json +coverage*.xml +*.trx + +# OS files +.DS_Store +Thumbs.db diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 0000000..f3604c1 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,67 @@ +name: Docker Build and Publish + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=sha,prefix={{branch}}- + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build Docker image + uses: docker/build-push-action@v6 + with: + context: . + push: false + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Push Docker image to GitHub Container Registry + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6835090 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,54 @@ +# Build stage +FROM mcr.microsoft.com/dotnet/sdk:9.0-bookworm-slim AS build +WORKDIR /src + +# Copy solution and project files +COPY ["AbeckDev.DbTimetable.sln", "./"] +COPY ["AbeckDev.DbTimetable.Mcp/AbeckDev.DbTimetable.Mcp.csproj", "AbeckDev.DbTimetable.Mcp/"] +COPY ["AbeckDev.DbTimetable.Mcp.Test/AbeckDev.DbTimetable.Mcp.Test.csproj", "AbeckDev.DbTimetable.Mcp.Test/"] + +# Restore dependencies +RUN dotnet restore "AbeckDev.DbTimetable.Mcp/AbeckDev.DbTimetable.Mcp.csproj" + +# Copy the rest of the source code +COPY . . + +# Build the application +WORKDIR "/src/AbeckDev.DbTimetable.Mcp" +RUN dotnet build "AbeckDev.DbTimetable.Mcp.csproj" -c Release -o /app/build + +# Publish stage +FROM build AS publish +RUN dotnet publish "AbeckDev.DbTimetable.Mcp.csproj" -c Release -o /app/publish /p:UseAppHost=false + +# Runtime stage +FROM mcr.microsoft.com/dotnet/aspnet:9.0-bookworm-slim AS final +WORKDIR /app + +# Install curl for healthcheck +RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/* + +# Create a non-root user +RUN groupadd -r appuser && useradd -r -g appuser appuser + +# Copy published files +COPY --from=publish /app/publish . + +# Change ownership to non-root user +RUN chown -R appuser:appuser /app + +# Switch to non-root user +USER appuser + +# Expose port +EXPOSE 3001 + +# Set environment variables +ENV ASPNETCORE_URLS=http://0.0.0.0:3001 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:3001/mcp || exit 1 + +# Entry point +ENTRYPOINT ["dotnet", "AbeckDev.DbTimetable.Mcp.dll"] diff --git a/README.md b/README.md index 71bfd51..5608258 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # DB-TimetableAPI-MCPServer [![.NET CI with Code Coverage](https://github.com/abeckDev/DB-TimetableAPI-MCPServer/actions/workflows/dotnet-ci.yml/badge.svg)](https://github.com/abeckDev/DB-TimetableAPI-MCPServer/actions/workflows/dotnet-ci.yml) +[![Docker Build and Publish](https://github.com/abeckDev/DB-TimetableAPI-MCPServer/actions/workflows/docker-publish.yml/badge.svg)](https://github.com/abeckDev/DB-TimetableAPI-MCPServer/actions/workflows/docker-publish.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![codecov](https://codecov.io/github/abeckDev/DB-TimetableAPI-MCPServer/graph/badge.svg?token=RJR3H1JRPW)](https://codecov.io/github/abeckDev/DB-TimetableAPI-MCPServer) @@ -187,6 +188,90 @@ dotnet build The project should build successfully. If you encounter any issues, ensure you have .NET 9.0 SDK installed. +### Using Docker (Recommended for Production) + +The easiest way to run the MCP server is using Docker. Pre-built images are automatically published to the GitHub Container Registry. + +#### Pull and Run the Latest Image + +```bash +# Pull the latest image from GitHub Container Registry +docker pull ghcr.io/abeckdev/db-timetableapi-mcpserver:latest + +# Run the container with your API credentials +docker run -d \ + --name db-timetable-mcp \ + -p 3001:3001 \ + -e DeutscheBahnApi__ClientId="your-actual-client-id" \ + -e DeutscheBahnApi__ApiKey="your-actual-api-key" \ + -e DeutscheBahnApi__BaseUrl="https://apis.deutschebahn.com/db-api-marketplace/apis/timetables/v1/" \ + ghcr.io/abeckdev/db-timetableapi-mcpserver:latest + +# Check if the container is running +docker ps + +# View logs +docker logs db-timetable-mcp +``` + +The MCP server will be accessible at `http://localhost:3001/mcp`. + +#### Build Your Own Docker Image + +If you prefer to build the Docker image locally: + +```bash +# Build the image +docker build -t db-timetableapi-mcpserver:local . + +# Run the container +docker run -d \ + --name db-timetable-mcp \ + -p 3001:3001 \ + -e DeutscheBahnApi__ClientId="your-actual-client-id" \ + -e DeutscheBahnApi__ApiKey="your-actual-api-key" \ + -e DeutscheBahnApi__BaseUrl="https://apis.deutschebahn.com/db-api-marketplace/apis/timetables/v1/" \ + db-timetableapi-mcpserver:local +``` + +#### Docker Compose + +You can also use Docker Compose for easier management. Create a `docker-compose.yml` file: + +```yaml +services: + mcp-server: + image: ghcr.io/abeckdev/db-timetableapi-mcpserver:latest + container_name: db-timetable-mcp + ports: + - "3001:3001" + environment: + - DeutscheBahnApi__ClientId=your-actual-client-id + - DeutscheBahnApi__ApiKey=your-actual-api-key + - DeutscheBahnApi__BaseUrl=https://apis.deutschebahn.com/db-api-marketplace/apis/timetables/v1/ + restart: unless-stopped +``` + +Then run: + +```bash +docker compose up -d +``` + +#### Docker Image Tags + +Images are tagged with multiple identifiers for flexibility: + +- `latest` - The most recent build from the main branch +- `main` - Same as latest, tracks the main branch +- `main-` - Specific commit SHA from the main branch (e.g., `main-abc1234`) + +Example pulling a specific version: + +```bash +docker pull ghcr.io/abeckdev/db-timetableapi-mcpserver:main-abc1234 +``` + --- ## ⚙️ Configuration @@ -680,9 +765,25 @@ All pull requests automatically run: - ✅ All unit tests - ✅ Code coverage analysis - ✅ Coverage threshold checks (70% minimum) +- ✅ Docker image build verification Coverage reports are available as workflow artifacts. +### Continuous Deployment + +When changes are pushed to the `main` branch: +- ✅ Docker images are automatically built +- ✅ Images are tagged with multiple identifiers (latest, main, commit SHA) +- ✅ Images are published to GitHub Container Registry (ghcr.io) +- ✅ Images are publicly accessible at `ghcr.io/abeckdev/db-timetableapi-mcpserver` + +The Docker image includes: +- Multi-stage build for optimized size +- .NET 9.0 runtime on Debian Bookworm (slim variant) +- Non-root user for enhanced security +- Health check endpoint +- Proper port exposure (3001) + --- ## 🤝 Contributing From 5572943f2cc07a01506c708e5b86f6b3b6ee8ab7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Nov 2025 23:12:13 +0000 Subject: [PATCH 3/4] Address code review feedback: improve health check and documentation Co-authored-by: abeckDev <8720854+abeckDev@users.noreply.github.com> --- .github/workflows/docker-publish.yml | 1 + Dockerfile | 2 +- README.md | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index f3604c1..380f51d 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -44,6 +44,7 @@ jobs: type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=raw,value=latest,enable={{is_default_branch}} + # Note: semver tags require Git tags in the format v1.2.3 - name: Build Docker image uses: docker/build-push-action@v6 diff --git a/Dockerfile b/Dockerfile index 6835090..ad80b36 100644 --- a/Dockerfile +++ b/Dockerfile @@ -48,7 +48,7 @@ ENV ASPNETCORE_URLS=http://0.0.0.0:3001 # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ - CMD curl -f http://localhost:3001/mcp || exit 1 + CMD curl -f http://localhost:3001/ || exit 1 # Entry point ENTRYPOINT ["dotnet", "AbeckDev.DbTimetable.Mcp.dll"] diff --git a/README.md b/README.md index 5608258..e42f997 100644 --- a/README.md +++ b/README.md @@ -269,7 +269,7 @@ Images are tagged with multiple identifiers for flexibility: Example pulling a specific version: ```bash -docker pull ghcr.io/abeckdev/db-timetableapi-mcpserver:main-abc1234 +docker pull ghcr.io/abeckdev/db-timetableapi-mcpserver:main-a1b2c3d ``` --- From 71ce4c23089e47a5c265024325e4a6944fe619c0 Mon Sep 17 00:00:00 2001 From: Alexander Beck Date: Thu, 6 Nov 2025 01:33:36 +0100 Subject: [PATCH 4/4] Fix Docker tag prefix to use github.ref_name --- .github/workflows/docker-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 380f51d..81a5031 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -40,7 +40,7 @@ jobs: tags: | type=ref,event=branch type=ref,event=pr - type=sha,prefix={{branch}}- + type=sha,prefix=${{ github.ref_name }}- type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=raw,value=latest,enable={{is_default_branch}}