From e3a0f8ff037100ea4456ff743a78fc04dbd8ecf5 Mon Sep 17 00:00:00 2001 From: Tim Pietrusky Date: Fri, 14 Nov 2025 21:47:49 +0100 Subject: [PATCH 1/2] feat(ci): add github actions workflow and service handling options - add github actions workflow for automated docker builds and pushes - add comprehensive documentation for entrypoint/service handling options - add run.sh script for running custom commands with base image services - update dockerfiles with detailed comments explaining three service options - update main.py to keep container running with signal handling - update readme with service options documentation and ci/cd setup - add docs/context.md with project architecture documentation --- .github/workflows/dev.yml | 74 +++++++++++++++ Dockerfile | 94 +++++++++++++------ Dockerfile.uv | 96 +++++++++++++------ README.md | 189 +++++++++++++++++++++++++------------- docs/context.md | 121 ++++++++++++++++++++++++ main.py | 65 ++++++++----- requirements.txt | 10 +- run.sh | 18 ++++ 8 files changed, 513 insertions(+), 154 deletions(-) create mode 100644 .github/workflows/dev.yml create mode 100644 docs/context.md create mode 100644 run.sh diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml new file mode 100644 index 0000000..97ba237 --- /dev/null +++ b/.github/workflows/dev.yml @@ -0,0 +1,74 @@ +name: Build and Push Docker Images + +on: + push: + branches: + - main + pull_request: + branches: + - "**" + workflow_dispatch: + +permissions: + contents: read + +env: + DOCKER_IMAGE: runpod/pod-template + DOCKER_PLATFORM: linux/amd64 + +jobs: + build-and-push: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Clean up runner disk space + run: | + echo "Cleaning up unnecessary folders to free disk space..." + rm -rf /usr/share/dotnet + rm -rf /usr/local/lib/android + rm -rf /opt/ghc + rm -rf /opt/hostedtoolcache/CodeQL + rm -rf "$AGENT_TOOLSDIRECTORY" + docker system prune -af || true + df -h + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push pip-based image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + platforms: ${{ env.DOCKER_PLATFORM }} + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ env.DOCKER_IMAGE }}:latest,${{ env.DOCKER_IMAGE }}:pip-latest + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Clean up Docker and disk space between builds + run: | + echo "Cleaning up Docker and disk space..." + docker system prune -af + docker builder prune -af + rm -rf /tmp/* + df -h + + - name: Build and push uv-based image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile.uv + platforms: ${{ env.DOCKER_PLATFORM }} + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ env.DOCKER_IMAGE }}:uv-latest + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/Dockerfile b/Dockerfile index bb59502..2c6a552 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,29 +1,65 @@ -# Use Runpod PyTorch base image -FROM runpod/pytorch:1.0.2-cu1281-torch280-ubuntu2404 - -# Set environment variables -ENV PYTHONUNBUFFERED=1 - -# Set the working directory -WORKDIR /app - -# Install system dependencies if needed -RUN apt-get update --yes && \ - DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-install-recommends \ - wget \ - && rm -rf /var/lib/apt/lists/* - -# Copy requirements file -COPY requirements.txt /app/ - -# Install Python dependencies with pip -# For uv alternative, see Dockerfile.uv -RUN pip install --no-cache-dir --upgrade pip && \ - pip install --no-cache-dir -r requirements.txt - -# Copy application files -COPY . /app - -# Set the default command -CMD ["python", "main.py"] - +# Use Runpod PyTorch base image +FROM runpod/pytorch:1.0.2-cu1281-torch280-ubuntu2404 + +# Set environment variables +ENV PYTHONUNBUFFERED=1 + +# Set the working directory +WORKDIR /app + +# Install system dependencies if needed +RUN apt-get update --yes && \ + DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-install-recommends \ + wget \ + && rm -rf /var/lib/apt/lists/* + +# Copy requirements file +COPY requirements.txt /app/ + +# Install Python dependencies with pip +# For uv alternative, see Dockerfile.uv +RUN pip install --no-cache-dir --upgrade pip && \ + pip install --no-cache-dir -r requirements.txt + +# Copy application files +COPY . /app + +# ============================================================================ +# OPTION 1: Keep everything from base image (Jupyter, SSH, entrypoint) - DEFAULT +# ============================================================================ +# The base image already provides everything: +# - Entrypoint: /opt/nvidia/nvidia_entrypoint.sh (handles CUDA setup) +# - Default CMD: /start.sh (starts Jupyter/SSH automatically based on template settings) +# - Jupyter Notebook (starts if startJupyter=true in template) +# - SSH access (starts if startSsh=true in template) +# +# Just don't override CMD - the base image handles everything! +# CMD is not set, so base image default (/start.sh) is used + +# ============================================================================ +# OPTION 2: Override CMD but keep entrypoint and services +# ============================================================================ +# If you want to run your own command but still have Jupyter/SSH start: +# - Keep the entrypoint (CUDA setup still happens automatically) +# - Use the provided run.sh script which starts /start.sh in background, +# then runs your application commands +# +# Edit run.sh to customize what runs after services start, then uncomment: +# COPY run.sh /app/run.sh +# RUN chmod +x /app/run.sh +# CMD ["/app/run.sh"] +# +# The run.sh script: +# 1. Starts /start.sh in background (starts Jupyter/SSH) +# 2. Waits for services to initialize +# 3. Runs your application commands +# 4. Waits for background processes + +# ============================================================================ +# OPTION 3: Override everything - no Jupyter, no SSH, just your app +# ============================================================================ +# If you don't want any base image services, override both entrypoint and CMD: +# +# ENTRYPOINT [] # Clear entrypoint +# CMD ["python", "/app/main.py"] + diff --git a/Dockerfile.uv b/Dockerfile.uv index 685ab1a..f8f16b2 100644 --- a/Dockerfile.uv +++ b/Dockerfile.uv @@ -1,30 +1,66 @@ -# Use Runpod PyTorch base image -FROM runpod/pytorch:1.0.2-cu1281-torch280-ubuntu2404 - -# Set environment variables -ENV PYTHONUNBUFFERED=1 - -# Set the working directory -WORKDIR /app - -# Install system dependencies if needed -RUN apt-get update --yes && \ - DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-install-recommends \ - wget \ - && rm -rf /var/lib/apt/lists/* - -# Install uv -RUN pip install --no-cache-dir uv - -# Copy requirements file -COPY requirements.txt /app/ - -# Install Python dependencies with uv -RUN uv pip install --system --break-system-packages -r requirements.txt - -# Copy application files -COPY . /app - -# Set the default command -CMD ["python", "main.py"] - +# Use Runpod PyTorch base image +FROM runpod/pytorch:1.0.2-cu1281-torch280-ubuntu2404 + +# Set environment variables +ENV PYTHONUNBUFFERED=1 + +# Set the working directory +WORKDIR /app + +# Install system dependencies if needed +RUN apt-get update --yes && \ + DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-install-recommends \ + wget \ + && rm -rf /var/lib/apt/lists/* + +# Install uv +RUN pip install --no-cache-dir uv + +# Copy requirements file +COPY requirements.txt /app/ + +# Install Python dependencies with uv +RUN uv pip install --system --break-system-packages -r requirements.txt + +# Copy application files +COPY . /app + +# ============================================================================ +# OPTION 1: Keep everything from base image (Jupyter, SSH, entrypoint) - DEFAULT +# ============================================================================ +# The base image already provides everything: +# - Entrypoint: /opt/nvidia/nvidia_entrypoint.sh (handles CUDA setup) +# - Default CMD: /start.sh (starts Jupyter/SSH automatically based on template settings) +# - Jupyter Notebook (starts if startJupyter=true in template) +# - SSH access (starts if startSsh=true in template) +# +# Just don't override CMD - the base image handles everything! +# CMD is not set, so base image default (/start.sh) is used + +# ============================================================================ +# OPTION 2: Override CMD but keep entrypoint and services +# ============================================================================ +# If you want to run your own command but still have Jupyter/SSH start: +# - Keep the entrypoint (CUDA setup still happens automatically) +# - Use the provided run.sh script which starts /start.sh in background, +# then runs your application commands +# +# Edit run.sh to customize what runs after services start, then uncomment: +# COPY run.sh /app/run.sh +# RUN chmod +x /app/run.sh +# CMD ["/app/run.sh"] +# +# The run.sh script: +# 1. Starts /start.sh in background (starts Jupyter/SSH) +# 2. Waits for services to initialize +# 3. Runs your application commands +# 4. Waits for background processes + +# ============================================================================ +# OPTION 3: Override everything - no Jupyter, no SSH, just your app +# ============================================================================ +# If you don't want any base image services, override both entrypoint and CMD: +# +# ENTRYPOINT [] # Clear entrypoint +# CMD ["python", "/app/main.py"] + diff --git a/README.md b/README.md index ea98aa6..4cb9cf4 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,123 @@ -# Runpod Template Example - -This is a clean template repository demonstrating how to create a Runpod template by extending a base image. - -## Structure - -- `Dockerfile` - Extends the Runpod PyTorch base image using pip (default) -- `Dockerfile.uv` - Alternative Dockerfile using uv for faster package installation -- `requirements.txt` - Python package dependencies (for pip) -- `main.py` - Example application entry point -- `.dockerignore` - Files to exclude from Docker build context - -## Base Image - -This template extends `runpod/pytorch:1.0.2-cu1281-torch280-ubuntu2404` which includes: -- PyTorch 2.8.0 -- CUDA 12.8.1 -- Ubuntu 24.04 - -## Installation Methods - -This template supports two package installation methods: - -### Option 1: Using pip (default) - -The `Dockerfile` uses `pip` by default. Add your dependencies to `requirements.txt` and build: - -```bash -docker build -t my-template . -``` - -### Option 2: Using uv - -To use `uv` for faster package installation, use `Dockerfile.uv`: - -```bash -docker build -f Dockerfile.uv -t my-template . -``` - -Make sure your dependencies are listed in `requirements.txt` (uv can read requirements.txt files). - -## Usage - -1. Customize `requirements.txt` with your Python dependencies -2. Add your application code -3. Build the Docker image: - ```bash - # Using pip (default) - docker build -t my-template . - - # Or using uv - docker build -f Dockerfile.uv -t my-template . - ``` -4. Test locally: - ```bash - docker run --rm my-template - ``` -5. Push to a container registry for use with Runpod - -## Customization - -- Modify the `FROM` line in the Dockerfile to use other Runpod base images -- Add system dependencies in the `apt-get install` section -- Update `requirements.txt` with your Python packages -- Replace `main.py` with your application entry point - +# Runpod Template Example + +This is a clean template repository demonstrating how to create a Runpod template by extending a base image. + +## Structure + +- `Dockerfile` - Extends the Runpod PyTorch base image using pip (default) +- `Dockerfile.uv` - Alternative Dockerfile using uv for faster package installation +- `requirements.txt` - Python package dependencies (for pip) +- `main.py` - Example application entry point +- `run.sh` - Optional script for running custom commands with base image services (Option 2) +- `.dockerignore` - Files to exclude from Docker build context +- `.github/workflows/dev.yml` - GitHub Actions workflow for automated builds + +## Base Image + +This template extends `runpod/pytorch:1.0.2-cu1281-torch280-ubuntu2404` which includes: + +- PyTorch 2.8.0 +- CUDA 12.8.1 +- Ubuntu 24.04 + +## Installation Methods + +This template supports two package installation methods: + +### Option 1: Using pip (default) + +The `Dockerfile` uses `pip` by default. Add your dependencies to `requirements.txt` and build: + +```bash +docker build --platform linux/amd64 -t my-template . +``` + +### Option 2: Using uv + +To use `uv` for faster package installation, use `Dockerfile.uv`: + +```bash +docker build --platform linux/amd64 -f Dockerfile.uv -t my-template . +``` + +Make sure your dependencies are listed in `requirements.txt` (uv can read requirements.txt files). + +**Note**: The `--platform linux/amd64` flag is required when building on non-Linux systems (macOS, ARM, etc.). + +## Entrypoint and Service Options + +The Dockerfiles demonstrate three approaches for handling the base image's entrypoint and services: + +### Option 1: Keep everything from base image (DEFAULT) + +Preserves all base image functionality (Jupyter, SSH, CUDA setup). The base image's `/start.sh` script automatically starts Jupyter/SSH based on template settings. No CMD override needed - just use the default. + +### Option 2: Override CMD but keep services + +If you want to run your own command but still have Jupyter/SSH start: + +1. Edit `run.sh` to customize what runs after services start +2. Uncomment the Option 2 lines in your Dockerfile: + ```dockerfile + COPY run.sh /app/run.sh + RUN chmod +x /app/run.sh + CMD ["/app/run.sh"] + ``` + +The `run.sh` script starts `/start.sh` in background, waits for services, then runs your commands. + +### Option 3: Override everything + +No Jupyter, no SSH - just your application. Override both entrypoint and CMD: + +```dockerfile +ENTRYPOINT [] # Clear entrypoint +CMD ["python", "/app/main.py"] +``` + +See the Dockerfiles for detailed comments on each option. + +## Usage + +1. Customize `requirements.txt` with your Python dependencies +2. Add your application code +3. Choose your entrypoint option (see above) +4. Build the Docker image: + + ```bash + # Using pip (default) + docker build --platform linux/amd64 -t my-template . + + # Or using uv + docker build --platform linux/amd64 -f Dockerfile.uv -t my-template . + ``` + +5. Test locally: + ```bash + docker run --rm --platform linux/amd64 my-template + ``` +6. Push to Docker Hub or your container registry for use with Runpod + +## Automated Builds + +This repository includes a GitHub Actions workflow (`.github/workflows/dev.yml`) that automatically: + +- Builds both pip and uv-based images on push to main +- Pushes images to Docker Hub as: + - `runpod/pod-template:latest` (pip-based) + - `runpod/pod-template:pip-latest` (pip-based) + - `runpod/pod-template:uv-latest` (uv-based) + +To use the workflow, add these GitHub secrets: + +- `DOCKERHUB_USERNAME` - Your Docker Hub username +- `DOCKERHUB_TOKEN` - Your Docker Hub access token + +## Customization + +- Modify the `FROM` line in the Dockerfile to use other Runpod base images +- Add system dependencies in the `apt-get install` section +- Update `requirements.txt` with your Python packages +- Replace `main.py` with your application entry point +- Choose entrypoint/service option (see Entrypoint and Service Options above) +- Customize `run.sh` if using Option 2 diff --git a/docs/context.md b/docs/context.md new file mode 100644 index 0000000..2ba1f83 --- /dev/null +++ b/docs/context.md @@ -0,0 +1,121 @@ +# Project Context + +## Overview + +This is a Runpod template repository that demonstrates how to create containerized applications for Runpod's GPU cloud platform. The template extends Runpod's PyTorch base image and provides a clean starting point for building custom GPU-accelerated applications. + +## Technology Stack + +- **Base Image**: `runpod/pytorch:1.0.2-cu1281-torch280-ubuntu2404` + - PyTorch 2.8.0 + - CUDA 12.8.1 + - Ubuntu 24.04 +- **Language**: Python 3.10+ +- **Containerization**: Docker +- **Package Management**: + - pip (default, via `Dockerfile`) + - uv (alternative, via `Dockerfile.uv`) + +## Project Structure + +``` +pod-template/ +├── Dockerfile # Default Dockerfile using pip +├── Dockerfile.uv # Alternative Dockerfile using uv +├── main.py # Application entry point +├── requirements.txt # Python dependencies (pip) +├── pyproject.toml # Project metadata (uv support) +├── .dockerignore # Files excluded from Docker build +└── docs/ # Documentation + └── context.md # This file +``` + +## Architecture + +### High-Level Flow + +1. **Base Image**: Extends Runpod's PyTorch base image with pre-installed CUDA and PyTorch +2. **Dependencies**: Installs Python packages from `requirements.txt` +3. **Application**: Copies application code into container +4. **Execution**: Runs `main.py` as the default command + +### Docker Build Options + +**Option 1: pip (default)** +- Uses `Dockerfile` +- Installs packages via `pip install -r requirements.txt` +- Standard Python package management + +**Option 2: uv (alternative)** +- Uses `Dockerfile.uv` +- Installs packages via `uv pip install -r requirements.txt` +- Faster package resolution and installation + +### Application Entry Point + +The `main.py` file serves as the application entry point. It: +- Verifies Python and PyTorch versions +- Checks CUDA availability +- Provides a template for custom application logic + +### Entrypoint and Service Management Options + +The Dockerfiles demonstrate three approaches for handling the base image's entrypoint and services: + +**Option 1: Keep everything from base image (DEFAULT)** +- Preserves all base image functionality (Jupyter, SSH, CUDA setup) +- Uses base image's entrypoint (`/opt/nvidia/nvidia_entrypoint.sh`) +- Uses `/start.sh` script which automatically starts Jupyter/SSH based on template settings +- Application runs after services start via `CMD ["/start.sh", "/app/run_app.sh"]` +- **Recommended**: Best for development and interactive use + +**Option 2: Override entrypoint but keep services** +- Clears the entrypoint but manually calls `/start.sh` to start services +- Useful when you need custom entrypoint logic but still want Jupyter/SSH +- Services run in background, then application runs +- See commented code in Dockerfiles for implementation + +**Option 3: Override everything** +- Clears both entrypoint and uses custom CMD +- No Jupyter, no SSH - just your application +- Minimal overhead, best for production serverless workloads +- See commented code in Dockerfiles for implementation + +## Key Files + +- **Dockerfile**: Primary container definition using pip +- **Dockerfile.uv**: Alternative container definition using uv for faster builds +- **requirements.txt**: Python package dependencies (used by both Dockerfiles) +- **main.py**: Application entry point executed when container runs +- **pyproject.toml**: Project metadata and dependency specification (for uv) +- **.dockerignore**: Excludes documentation, git files, and build artifacts from Docker context + +## Development Workflow + +1. **Add Dependencies**: Update `requirements.txt` with Python packages +2. **Write Application Code**: Modify `main.py` or add additional Python modules +3. **Build Container**: + - `docker build -t my-template .` (pip) + - `docker build -f Dockerfile.uv -t my-template .` (uv) +4. **Test Locally**: `docker run --rm my-template` +5. **Deploy**: Push to container registry for Runpod use + +## Important Notes for Contributors + +- The base image includes PyTorch and CUDA - no need to install these separately +- Both Dockerfiles install the same dependencies from `requirements.txt` +- The `.dockerignore` excludes `docs/` and `*.md` files from Docker builds +- System dependencies can be added in the `apt-get install` section of Dockerfiles +- **Default behavior (Option 1)**: Uses `/start.sh` which starts Jupyter/SSH automatically when enabled in template +- To change entrypoint behavior, see the three options documented in the Dockerfiles +- `PYTHONUNBUFFERED=1` ensures Python output is immediately visible in logs +- The base image entrypoint (`/opt/nvidia/nvidia_entrypoint.sh`) handles CUDA initialization + +## Customization Points + +- **Base Image**: Change `FROM` line to use other Runpod base images +- **System Packages**: Add to `apt-get install` section +- **Python Dependencies**: Update `requirements.txt` +- **Application Code**: Replace or extend `main.py` +- **Entry Point**: Modify `CMD` in Dockerfile + diff --git a/main.py b/main.py index 40f86fb..990157d 100644 --- a/main.py +++ b/main.py @@ -1,24 +1,41 @@ -""" -Example template application. -This demonstrates how to extend a Runpod PyTorch base image. -""" - -import sys -import torch - -def main(): - print("Hello from your Runpod template!") - print(f"Python version: {sys.version.split()[0]}") - print(f"PyTorch version: {torch.__version__}") - print(f"CUDA available: {torch.cuda.is_available()}") - - if torch.cuda.is_available(): - print(f"CUDA version: {torch.version.cuda}") - print(f"GPU device: {torch.cuda.get_device_name(0)}") - - # Add your application logic here - return 0 - -if __name__ == "__main__": - sys.exit(main()) - +""" +Example template application. +This demonstrates how to extend a Runpod PyTorch base image. +""" + +import sys +import torch +import time +import signal + +def main(): + print("Hello from your Runpod template!") + print(f"Python version: {sys.version.split()[0]}") + print(f"PyTorch version: {torch.__version__}") + print(f"CUDA available: {torch.cuda.is_available()}") + + if torch.cuda.is_available(): + print(f"CUDA version: {torch.version.cuda}") + print(f"GPU device: {torch.cuda.get_device_name(0)}") + + print("\nContainer is running. Add your application logic here.") + print("Press Ctrl+C to stop.") + + # Keep container running + def signal_handler(sig, frame): + print("\nShutting down...") + sys.exit(0) + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + # Keep running until terminated + try: + while True: + time.sleep(60) + except KeyboardInterrupt: + signal_handler(None, None) + +if __name__ == "__main__": + main() + diff --git a/requirements.txt b/requirements.txt index f43cf35..21ef1db 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -# Python dependencies -# Add your packages here -numpy>=1.24.0 -requests>=2.31.0 - +# Python dependencies +# Add your packages here +numpy>=1.24.0 +requests>=2.31.0 + diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..c0672fc --- /dev/null +++ b/run.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# This script runs /start.sh in the background to start Jupyter/SSH services, +# then executes your application. +# +# Usage: Modify this file to add your own commands after services start. + +# Start base image services (Jupyter/SSH) in background +/start.sh & + +# Wait a moment for services to start +sleep 2 + +# Add your application commands here +python /app/main.py + +# Wait for background processes +wait + From 1beb9384eb313652adeeacd2c8606fff377f14c0 Mon Sep 17 00:00:00 2001 From: Tim Pietrusky Date: Fri, 14 Nov 2025 21:52:46 +0100 Subject: [PATCH 2/2] chore(ci): remove android cleanup from disk space step --- .github/workflows/dev.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 97ba237..c522558 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -27,7 +27,6 @@ jobs: run: | echo "Cleaning up unnecessary folders to free disk space..." rm -rf /usr/share/dotnet - rm -rf /usr/local/lib/android rm -rf /opt/ghc rm -rf /opt/hostedtoolcache/CodeQL rm -rf "$AGENT_TOOLSDIRECTORY"