Skip to content

vbrunelle/NoHands

Repository files navigation

NoHands

License Python Django

NoHands is a powerful Django-based web interface for automating Docker image builds and deployments from Git repositories. It leverages Dagger for reproducible builds and provides both a user-friendly web interface and a comprehensive REST API for complete automation.

πŸš€ Features

  • Repository Management: Browse Git repositories, branches, and commits with automatic synchronization
  • Build Pipeline: Trigger Dagger-powered Docker image builds from any commit with one click
  • Build History: Track build status, logs, and image tags with detailed execution history
  • REST API: Full REST API access for automation and CI/CD integration
  • Admin Interface: Django admin for easy configuration and management
  • Real-time Logs: View build logs and status updates in real-time
  • Registry Integration: Push built images directly to Docker registries
  • Multi-branch Support: Build from any branch or commit in your repository

πŸ“‹ Table of Contents

πŸ—οΈ Architecture

NoHands is built on Django 5+ and uses a modular architecture with three main applications:

Django Apps

  • projects: Manages Git repositories, branches, and commits

    • Repository cloning and caching
    • Branch and commit synchronization
    • Git operations via GitPython
  • builds: Handles build requests, execution, and history

    • Build queue management
    • Dagger pipeline execution
    • Build logs and status tracking
  • api: REST API endpoints using Django REST Framework

    • Full CRUD operations
    • Build triggering and monitoring
    • API authentication and permissions

Key Components

  • Git Utilities (projects/git_utils.py): GitPython-based functions for repository operations

    • Clone or update repositories
    • List branches and commits
    • Checkout specific commits
    • Repository information retrieval
  • Dagger Pipeline (builds/dagger_pipeline.py): Async Docker image building with Dagger SDK

    • Container building from Dockerfile
    • Registry authentication and push
    • Build result handling
    • Error management
  • Django Models:

    • GitRepository: Repository configuration and metadata
    • Branch: Branch tracking and latest commit info
    • Commit: Commit details (SHA, message, author, timestamp)
    • Build: Build requests, status, logs, and results
  • REST API: Full CRUD and build triggering via API with pagination and filtering

βœ… Prerequisites

Before installing NoHands, ensure you have the following installed:

Required Software

  • Python 3.11 or higher

    python --version  # Should show Python 3.11.x or higher
  • Git

    git --version  # Should show git version 2.x or higher
  • Docker (for Dagger builds)

    docker --version  # Should show Docker version 20.x or higher
    docker ps        # Should connect without errors
  • Dagger (installed via Python SDK during pip install)

    • Dagger will be automatically installed with the Python dependencies
    • Requires Docker to be running

System Requirements

  • Operating System: Linux, macOS, or Windows (with WSL2)
  • RAM: Minimum 2GB, recommended 4GB+ for building large projects
  • Disk Space: Minimum 5GB for application and Docker images
  • Network: Internet connection for cloning repositories and pulling Docker images

Optional but Recommended

  • Virtual Environment: Use venv or virtualenv to isolate dependencies
  • PostgreSQL: For production deployments (SQLite is used by default for development)
  • Redis: For task queues in production (if using Celery)

Requirements

  • Python 3.11+
  • Django 5+
  • Dagger (installed via Python SDK)
  • Git
  • Docker (for Dagger builds)

πŸ“¦ Installation

Step 1: Clone the Repository

git clone https://github.com/vbrunelle/NoHands.git
cd NoHands

Step 2: Create a Virtual Environment (Recommended)

# Create virtual environment
python -m venv venv

# Activate virtual environment
# On Linux/macOS:
source venv/bin/activate
# On Windows:
venv\Scripts\activate

Step 3: Install Python Dependencies

pip install -r requirements.txt

This will install:

  • Django 5.0+
  • Django REST Framework
  • Dagger SDK (for Docker builds)
  • GitPython (for Git operations)
  • And other required dependencies

Step 4: Set Up Environment Variables (Optional)

Create a .env file in the project root for environment-specific configuration:

# .env file
DJANGO_SECRET_KEY=your-secret-key-here
DJANGO_DEBUG=True
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1

# Docker Registry (optional)
DOCKER_REGISTRY=registry.example.com
DOCKER_REGISTRY_USERNAME=your-username
DOCKER_REGISTRY_PASSWORD=your-password

# Build Configuration
MAX_CONCURRENT_BUILDS=2

Step 5: Run Database Migrations

python manage.py migrate

This creates the SQLite database and all necessary tables.

Step 6: Set Up GitHub OAuth (Required)

Important: GitHub OAuth authentication is required for users to connect and manage repositories.

  1. Create a GitHub OAuth App:

    • Go to https://github.com/settings/developers
    • Click "New OAuth App"
    • Set Homepage URL to: http://localhost:8000/
    • Set Authorization callback URL to: http://localhost:8000/accounts/github/login/callback/
    • Note your Client ID and generate a Client Secret
  2. Configure NoHands:

# Set environment variables
export GITHUB_CLIENT_ID="your_github_client_id"
export GITHUB_CLIENT_SECRET="your_github_client_secret"

# Run setup command
python manage.py setup_github_oauth

The setup command will automatically configure the OAuth application in the database.

See GITHUB_OAUTH.md for detailed instructions.

Step 7: Create a Superuser Account (Optional)

While users can authenticate via GitHub, you may want to create a Django admin account:

python manage.py createsuperuser

Follow the prompts to create an admin account:

Note: The first user to connect via GitHub automatically becomes a superuser.

Step 8: Start the Development Server

python manage.py runserver

The server will start at http://localhost:8000/

Step 9: Verify Installation

Open your browser and navigate to:

Log in with the superuser credentials you created.

🐳 Docker Deployment

NoHands can be deployed using Docker for easy containerization and deployment.

Quick Start with Docker

  1. Build the Docker image:

    docker build -t nohands .
  2. Run the container:

    docker run -p 8000:8000 nohands
  3. Access the application:

Docker with Environment Variables

For production-like deployments, configure environment variables:

docker run -p 8000:8000 \
  -e DJANGO_SECRET_KEY="your-long-random-secret-key-minimum-50-characters" \
  -e DJANGO_DEBUG=False \
  -e DJANGO_ALLOWED_HOSTS="yourdomain.com,localhost" \
  -e GITHUB_CLIENT_ID="your_github_client_id" \
  -e GITHUB_CLIENT_SECRET="your_github_client_secret" \
  nohands

Docker with Persistent Data

To persist data (database and Git checkouts) between container restarts:

docker run -p 8000:8000 \
  -v nohands_data:/app/db.sqlite3 \
  -v nohands_tmp:/app/tmp \
  -e DJANGO_SECRET_KEY="your-secret-key" \
  nohands

Docker with Docker Socket (for Dagger builds)

To enable Dagger builds from within the container, mount the Docker socket:

docker run -p 8000:8000 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -e DJANGO_SECRET_KEY="your-secret-key" \
  nohands

Docker Compose Example

Create a docker-compose.yml file:

version: '3.8'

services:
  nohands:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DJANGO_SECRET_KEY=your-long-random-secret-key-here
      - DJANGO_DEBUG=False
      - DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1
    volumes:
      - nohands_data:/app
      - /var/run/docker.sock:/var/run/docker.sock
    restart: unless-stopped

volumes:
  nohands_data:

Run with:

docker-compose up -d

Available Environment Variables

Variable Description Default
DJANGO_SECRET_KEY Secret key for Django (required in production) Development key
DJANGO_DEBUG Enable debug mode True
DJANGO_ALLOWED_HOSTS Comma-separated allowed hosts Empty
DOCKER_REGISTRY Docker registry URL Empty
DOCKER_REGISTRY_USERNAME Registry username Empty
DOCKER_REGISTRY_PASSWORD Registry password Empty
GITHUB_CLIENT_ID GitHub OAuth Client ID Empty
GITHUB_CLIENT_SECRET GitHub OAuth Client Secret Empty
MAX_CONCURRENT_BUILDS Maximum concurrent builds 1

Production Recommendations

For production Docker deployments:

  1. Use a production WSGI server: Replace the development server with Gunicorn:

    # In your custom Dockerfile
    RUN pip install gunicorn
    CMD ["gunicorn", "nohands_project.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "4"]
  2. Use PostgreSQL: Configure a PostgreSQL database instead of SQLite.

  3. Set up a reverse proxy: Use Nginx or Traefik in front of the application.

  4. Enable HTTPS: Configure SSL/TLS certificates.

  5. Set proper secrets: Always use strong, unique secret keys in production.

πŸ“¦ Using Pre-built Images from GitHub Container Registry (GHCR)

NoHands images are automatically built and published to GitHub Container Registry when changes are pushed to the main branch or when tags are created.

Pulling the Image

# Pull the latest image from main branch
docker pull ghcr.io/vbrunelle/nohands:main

# Pull a specific version
docker pull ghcr.io/vbrunelle/nohands:v1.0.0

# Pull by commit SHA
docker pull ghcr.io/vbrunelle/nohands:sha-abc1234

Running the Pre-built Image

docker run -p 8000:8000 \
  -e DJANGO_SECRET_KEY="your-secret-key" \
  -v /var/run/docker.sock:/var/run/docker.sock \
  ghcr.io/vbrunelle/nohands:main

Docker Compose with GHCR Image

A docker-compose.yml file is included in the repository to easily start the application using the pre-built GHCR image.

Start the application:

# Start the application
docker compose up -d

# View logs
docker compose logs -f

# Stop the application
docker compose down

Optionally, create a .env file for custom configuration:

# .env (optional - for production deployments)
DJANGO_SECRET_KEY=your-long-random-secret-key-minimum-50-characters
DJANGO_DEBUG=False
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,yourdomain.com
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret

⚠️ Security Note: The Docker socket is mounted to allow Dagger to build Docker images. This provides root-level access to the host system. Only use in trusted environments.

πŸ” Private Package Access (Human Actions Required)

If the repository is private, the GHCR package will be private by default. To use private images:

  1. Generate a Personal Access Token (PAT):

  2. Login to GHCR:

    echo $GITHUB_PAT | docker login ghcr.io -u YOUR_USERNAME --password-stdin
  3. Pull the private image:

    docker pull ghcr.io/vbrunelle/nohands:main

Package Visibility Configuration (Repository Admin)

To manage the package visibility:

  1. Navigate to the repository's Packages section
  2. Click on the package (e.g., nohands)
  3. Go to Package settings
  4. Under Danger Zone, you can change visibility:
    • Private: Only repository collaborators can access
    • Public: Anyone can pull the image

Note: The GitHub Actions workflow uses GITHUB_TOKEN which has automatic packages:write permission. No additional secrets configuration is needed for the CI/CD pipeline.

🎯 Quick Start

Let's walk through building your first Docker image with NoHands!

1. Add Your First Repository

  1. Go to the Admin Panel: http://localhost:8000/admin/
  2. Click on Git Repositories β†’ Add Git Repository
  3. Fill in the form:
    • Name: my-project
    • URL: https://github.com/username/my-project.git or a local path like /path/to/repo
    • Description: My first project
    • Default Branch: main (or master)
    • Dockerfile Path: Dockerfile (path to Dockerfile in your repo)
    • Is Active: βœ… checked
  4. Click Save

2. Refresh Branches

  1. Go to the main page: http://localhost:8000/
  2. Click on your repository name
  3. Click Refresh Branches button
  4. Wait for branches to be fetched from Git

3. View Commits

  1. Click on any branch name (e.g., main)
  2. Click Refresh Commits to fetch latest commits
  3. You'll see a list of commits with their messages, authors, and timestamps

4. Trigger a Build

  1. In the commits list, click the πŸš€ Build button next to any commit
  2. Configure build options:
    • Push to Registry: Check if you want to push to a Docker registry (requires registry configuration)
    • Leave unchecked for local builds
  3. Click Start Build

5. Monitor the Build

  1. You'll be redirected to the build detail page
  2. The status will change from pending β†’ running β†’ success or failed
  3. View real-time logs as the build progresses
  4. Check the build duration and image tag when complete

That's it! You've successfully built your first Docker image with NoHands.

Installation

  1. Clone the repository:
git clone https://github.com/vbrunelle/NoHands.git
cd NoHands
  1. Install dependencies:
pip install -r requirements.txt
  1. Run migrations:
python manage.py migrate
  1. Create superuser (for admin access):
python manage.py createsuperuser
  1. Run the development server:
python manage.py runserver
  1. Access the application:

βš™οΈ Configuration

Environment Variables

NoHands can be configured using environment variables. Create a .env file or set them in your shell:

Django Configuration

# Secret key for cryptographic signing (REQUIRED in production)
DJANGO_SECRET_KEY="your-long-random-secret-key-minimum-50-characters"

# Debug mode (set to False in production)
DJANGO_DEBUG=False

# Allowed hosts (comma-separated, required when DEBUG=False)
DJANGO_ALLOWED_HOSTS="example.com,www.example.com,localhost"

Docker Registry Settings

Configure these to enable pushing images to a Docker registry:

# Docker registry URL (without protocol)
DOCKER_REGISTRY="registry.example.com"
# Or for Docker Hub:
DOCKER_REGISTRY="docker.io"

# Registry authentication
DOCKER_REGISTRY_USERNAME="your-username"
DOCKER_REGISTRY_PASSWORD="your-password-or-token"

Build Configuration

# Maximum concurrent builds (default: 1)
# Increase if you have sufficient resources
MAX_CONCURRENT_BUILDS=3

Settings File Configuration

You can also modify nohands_project/settings.py directly:

Git Checkout Directory

# Directory for temporary Git checkouts
GIT_CHECKOUT_DIR = BASE_DIR / 'tmp' / 'git_checkouts'

This directory will contain:

  • cache/: Cached repository clones
  • builds/: Temporary checkouts for each build

Database Configuration

Development (default):

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

Production (PostgreSQL recommended):

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'nohands',
        'USER': 'nohands_user',
        'PASSWORD': 'secure_password',
        'HOST': 'localhost',
        'PORT': '5432',
    }
}

REST Framework Settings

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 20,
    
    # Add authentication (optional)
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication',
    ],
    
    # Add permissions (optional)
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
}

Repository Configuration

Each Git repository can be configured with:

Field Description Example
name Unique repository identifier my-app
url Git URL or local path https://github.com/user/repo.git or /path/to/repo
description Human-readable description Production application
default_branch Default branch name main, master, develop
dockerfile_path Path to Dockerfile in repo Dockerfile, docker/Dockerfile, build/Dockerfile
is_active Whether repo is active True or False

Dockerfile Requirements

Your repository must contain a valid Dockerfile. Here's a minimal example:

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

See Dockerfile.example in the repository for a complete template.

Configuration

Environment Variables

You can configure the following via environment variables:

# Docker Registry Settings
export DOCKER_REGISTRY="registry.example.com"
export DOCKER_REGISTRY_USERNAME="your-username"
export DOCKER_REGISTRY_PASSWORD="your-password"

# Concurrent builds limit
export MAX_CONCURRENT_BUILDS=1

Settings

Key settings in nohands_project/settings.py:

  • GIT_CHECKOUT_DIR: Directory for temporary Git checkouts
  • DOCKER_REGISTRY: Docker registry URL
  • MAX_CONCURRENT_BUILDS: Maximum concurrent build jobs

🎨 Usage Examples

Web Interface Workflows

Workflow 1: Building from a Specific Commit

Use Case: You want to build a Docker image from a specific commit in your repository.

  1. Navigate to Repository

  2. Select Branch

    • Click on the branch containing your commit (e.g., main)
    • If you don't see branches, click Refresh Branches first
  3. Find Your Commit

    • Browse the commit list or click Refresh Commits to get the latest
    • Identify your commit by message, author, or timestamp
  4. Trigger Build

    • Click the πŸš€ Build button next to the commit
    • Configure options:
      • ☐ Push to Registry: Leave unchecked for local builds
      • β˜‘ Push to Registry: Check to push to configured registry
    • Click Start Build
  5. Monitor Progress

    • View build status in real-time
    • Check logs for detailed output
    • Note the image tag when complete (e.g., my-app:a1b2c3d4)

Workflow 2: Building and Pushing to Registry

Use Case: Build an image and push it to Docker Hub or a private registry.

  1. Configure Registry (one-time setup)

    • Set environment variables:
      export DOCKER_REGISTRY="registry.example.com"
      export DOCKER_REGISTRY_USERNAME="myusername"
      export DOCKER_REGISTRY_PASSWORD="mytoken"
    • Restart the Django server
  2. Trigger Build with Push

    • Follow steps from Workflow 1
    • When configuring the build, check β˜‘ Push to Registry
    • Click Start Build
  3. Verify in Registry

    • After successful build, the image will be pushed
    • Image tag format: registry.example.com/my-app:a1b2c3d4
    • Check your registry dashboard to verify

Workflow 3: Building from Multiple Branches

Use Case: You maintain multiple environments (dev, staging, prod) in different branches.

  1. Add Repository (if not already added)

    • Admin β†’ Git Repositories β†’ Add
    • Configure as shown in Quick Start
  2. Refresh Branches

    • Navigate to repository detail
    • Click Refresh Branches
    • All branches will be synced
  3. Build from Each Branch

    • Click on develop branch β†’ Refresh Commits β†’ Build from latest
    • Click on staging branch β†’ Refresh Commits β†’ Build from latest
    • Click on main branch β†’ Refresh Commits β†’ Build from latest
  4. Track All Builds

Workflow 4: Monitoring Build History

Use Case: Review past builds and their results.

  1. View All Builds

  2. Filter Builds

    • Builds are listed with:
      • Build ID
      • Repository name
      • Commit SHA
      • Branch name
      • Status (pending/running/success/failed)
      • Created timestamp
  3. View Build Details

    • Click on any build ID
    • See complete information:
      • Full build logs
      • Error messages (if failed)
      • Image tag generated
      • Build duration
      • Configuration used
  4. Analyze Failures

    • Check error messages in failed builds
    • Review logs to debug issues
    • Identify patterns in failures

REST API Examples

The REST API enables full automation and integration with CI/CD pipelines.

Example 1: List All Repositories

Request:

curl http://localhost:8000/api/repositories/

Response:

{
  "count": 2,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": 1,
      "name": "my-app",
      "url": "https://github.com/user/my-app.git",
      "description": "My application",
      "default_branch": "main",
      "is_active": true,
      "created_at": "2025-01-15T10:00:00Z"
    },
    {
      "id": 2,
      "name": "another-app",
      "url": "https://github.com/user/another-app.git",
      "description": "Another application",
      "default_branch": "master",
      "is_active": true,
      "created_at": "2025-01-16T11:00:00Z"
    }
  ]
}

Example 2: Get Repository Details

Request:

curl http://localhost:8000/api/repositories/1/

Response:

{
  "id": 1,
  "name": "my-app",
  "url": "https://github.com/user/my-app.git",
  "description": "My application",
  "default_branch": "main",
  "is_active": true,
  "created_at": "2025-01-15T10:00:00Z"
}

Example 3: List Branches for a Repository

Request:

curl "http://localhost:8000/api/branches/?repository=1"

Response:

{
  "count": 3,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": 1,
      "repository_name": "my-app",
      "name": "main",
      "commit_sha": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0",
      "last_updated": "2025-01-15T12:00:00Z"
    },
    {
      "id": 2,
      "repository_name": "my-app",
      "name": "develop",
      "commit_sha": "b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1",
      "last_updated": "2025-01-15T12:00:00Z"
    },
    {
      "id": 3,
      "repository_name": "my-app",
      "name": "feature/new-ui",
      "commit_sha": "c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2",
      "last_updated": "2025-01-15T12:00:00Z"
    }
  ]
}

Example 4: List Commits for a Branch

Request:

curl "http://localhost:8000/api/commits/?branch=1"

Response:

{
  "count": 50,
  "next": "http://localhost:8000/api/commits/?branch=1&page=2",
  "previous": null,
  "results": [
    {
      "id": 101,
      "repository_name": "my-app",
      "branch_name": "main",
      "sha": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0",
      "message": "Add new feature",
      "author": "John Doe",
      "author_email": "john@example.com",
      "committed_at": "2025-01-15T14:30:00Z"
    },
    {
      "id": 100,
      "repository_name": "my-app",
      "branch_name": "main",
      "sha": "b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1",
      "message": "Fix bug in authentication",
      "author": "Jane Smith",
      "author_email": "jane@example.com",
      "committed_at": "2025-01-14T16:45:00Z"
    }
  ]
}

Example 5: Trigger a Build (Most Important!)

Request:

curl -X POST http://localhost:8000/api/builds/trigger/ \
  -H "Content-Type: application/json" \
  -d '{
    "repository_id": 1,
    "commit_id": 101,
    "push_to_registry": false,
    "deploy_after_build": false
  }'

Response:

{
  "id": 42,
  "repository_name": "my-app",
  "commit_sha": "a1b2c3d4",
  "branch_name": "main",
  "status": "pending",
  "image_tag": "",
  "logs": "",
  "error_message": "",
  "created_at": "2025-01-15T15:00:00Z",
  "started_at": null,
  "completed_at": null,
  "push_to_registry": false,
  "duration": "N/A"
}

Example 6: Get Build Status

Request:

curl http://localhost:8000/api/builds/42/

Response (Running):

{
  "id": 42,
  "repository_name": "my-app",
  "commit_sha": "a1b2c3d4",
  "branch_name": "main",
  "status": "running",
  "image_tag": "",
  "logs": "Starting build for my-app:a1b2c3d4\nBuilding image from Dockerfile\n...",
  "error_message": "",
  "created_at": "2025-01-15T15:00:00Z",
  "started_at": "2025-01-15T15:00:05Z",
  "completed_at": null,
  "push_to_registry": false,
  "duration": "N/A"
}

Response (Success):

{
  "id": 42,
  "repository_name": "my-app",
  "commit_sha": "a1b2c3d4",
  "branch_name": "main",
  "status": "success",
  "image_tag": "my-app:a1b2c3d4",
  "logs": "Starting build for my-app:a1b2c3d4\nBuilding image from Dockerfile\n...\nBuild completed successfully in 125.45 seconds",
  "error_message": "",
  "created_at": "2025-01-15T15:00:00Z",
  "started_at": "2025-01-15T15:00:05Z",
  "completed_at": "2025-01-15T15:02:10Z",
  "push_to_registry": false,
  "duration": "2m 5s"
}

Example 7: List All Builds

Request:

curl http://localhost:8000/api/builds/

With Filtering:

# Filter by repository
curl "http://localhost:8000/api/builds/?repository=1"

# Filter by status
curl "http://localhost:8000/api/builds/?status=success"

# Combine filters
curl "http://localhost:8000/api/builds/?repository=1&status=failed"

Example 8: CI/CD Integration Script

Here's a complete script for CI/CD integration:

#!/bin/bash
# build_and_deploy.sh - Trigger NoHands build from CI/CD

NOHANDS_URL="http://localhost:8000"
REPOSITORY_ID=1
COMMIT_SHA="$CI_COMMIT_SHA"  # From your CI system

# 1. Find the commit ID
COMMIT_ID=$(curl -s "${NOHANDS_URL}/api/commits/?repository=${REPOSITORY_ID}" | \
  jq -r ".results[] | select(.sha==\"${COMMIT_SHA}\") | .id")

if [ -z "$COMMIT_ID" ]; then
  echo "Error: Commit not found"
  exit 1
fi

# 2. Trigger build
BUILD_RESPONSE=$(curl -s -X POST "${NOHANDS_URL}/api/builds/trigger/" \
  -H "Content-Type: application/json" \
  -d "{
    \"repository_id\": ${REPOSITORY_ID},
    \"commit_id\": ${COMMIT_ID},
    \"push_to_registry\": true,
    \"deploy_after_build\": false
  }")

BUILD_ID=$(echo "$BUILD_RESPONSE" | jq -r '.id')
echo "Build triggered: #${BUILD_ID}"

# 3. Poll for completion
while true; do
  BUILD_STATUS=$(curl -s "${NOHANDS_URL}/api/builds/${BUILD_ID}/" | jq -r '.status')
  
  echo "Build status: ${BUILD_STATUS}"
  
  if [ "$BUILD_STATUS" = "success" ]; then
    echo "Build completed successfully!"
    exit 0
  elif [ "$BUILD_STATUS" = "failed" ]; then
    echo "Build failed!"
    curl -s "${NOHANDS_URL}/api/builds/${BUILD_ID}/" | jq -r '.error_message'
    exit 1
  fi
  
  sleep 10
done

Example 9: Python Integration

import requests
import time

class NoHandsClient:
    def __init__(self, base_url):
        self.base_url = base_url.rstrip('/')
    
    def trigger_build(self, repository_id, commit_id, push_to_registry=False):
        """Trigger a new build."""
        url = f"{self.base_url}/api/builds/trigger/"
        data = {
            "repository_id": repository_id,
            "commit_id": commit_id,
            "push_to_registry": push_to_registry,
            "deploy_after_build": False
        }
        response = requests.post(url, json=data)
        response.raise_for_status()
        return response.json()
    
    def get_build_status(self, build_id):
        """Get build status."""
        url = f"{self.base_url}/api/builds/{build_id}/"
        response = requests.get(url)
        response.raise_for_status()
        return response.json()
    
    def wait_for_build(self, build_id, timeout=3600, poll_interval=10):
        """Wait for build to complete."""
        start_time = time.time()
        
        while time.time() - start_time < timeout:
            build = self.get_build_status(build_id)
            status = build['status']
            
            print(f"Build {build_id}: {status}")
            
            if status == 'success':
                return build
            elif status == 'failed':
                raise Exception(f"Build failed: {build['error_message']}")
            
            time.sleep(poll_interval)
        
        raise TimeoutError(f"Build {build_id} timed out after {timeout}s")

# Usage
client = NoHandsClient("http://localhost:8000")

# Trigger build
build = client.trigger_build(repository_id=1, commit_id=101, push_to_registry=True)
print(f"Build triggered: #{build['id']}")

# Wait for completion
result = client.wait_for_build(build['id'])
print(f"Build completed: {result['image_tag']}")

Usage

Web Interface

  1. Add a Repository:

    • Go to Admin panel (http://localhost:8000/admin/)
    • Add a new Git Repository with URL and configuration
    • Example: https://github.com/user/repo.git or local path
  2. Browse Branches:

    • View repository details
    • Click "Refresh Branches" to fetch from Git
    • Select a branch to view commits
  3. Trigger a Build:

    • Navigate to a branch's commits
    • Click "πŸš€ Build" next to any commit
    • Configure build options (push to registry, etc.)
    • Start the build
  4. Monitor Builds:

    • View all builds at /builds/
    • Click a build to see detailed logs
    • Builds run asynchronously in background threads

REST API

List Repositories

curl http://localhost:8000/api/repositories/

List Branches

curl http://localhost:8000/api/branches/?repository=1

List Commits

curl http://localhost:8000/api/commits/?branch=1

List Builds

curl http://localhost:8000/api/builds/

Trigger a Build

curl -X POST http://localhost:8000/api/builds/trigger/ \
  -H "Content-Type: application/json" \
  -d '{
    "repository_id": 1,
    "commit_id": 1,
    "push_to_registry": false,
    "deploy_after_build": false
  }'

Get Build Details

curl http://localhost:8000/api/builds/1/

CLI Commands

NoHands provides a comprehensive CLI suite using Django management commands that allows performing all tasks available in the UI. This is especially useful for automation, scripting, and agent-based workflows.

Repository Commands

repo_list - List All Repositories
# List all repositories
python manage.py repo_list

# List only active repositories
python manage.py repo_list --active-only

# Output in JSON format
python manage.py repo_list --format=json

Output Example:

Found 2 repository(ies):

ID     Name                           Default Branch  Active   URL
----------------------------------------------------------------------------------------------------
1      my-app                         main            βœ“        https://github.com/user/my-app.git
2      api-service                    develop         βœ“        https://github.com/user/api.git
repo_connect - Connect a Repository
# Basic usage
python manage.py repo_connect my-app https://github.com/user/my-app.git

# With all options
python manage.py repo_connect my-app https://github.com/user/my-app.git \
    --description="My application" \
    --default-branch=main \
    --dockerfile-path=Dockerfile \
    --user=admin

Options:

  • --description: Repository description
  • --default-branch: Default branch name (default: main)
  • --dockerfile-path: Path to Dockerfile in repo (default: Dockerfile)
  • --inactive: Create repository as inactive
  • --github-id: GitHub repository ID (optional)
  • --user: Username to associate with the repository
repo_refresh - Refresh Repository Branches
# Refresh branches by repository ID
python manage.py repo_refresh 1

# Refresh branches by repository name
python manage.py repo_refresh my-app

# Output in JSON format
python manage.py repo_refresh my-app --format=json
branch_commits - List Commits for a Branch
# List commits for default branch
python manage.py branch_commits my-app

# List commits for specific branch
python manage.py branch_commits my-app --branch=develop

# Refresh commits from Git before listing
python manage.py branch_commits my-app --refresh

# Limit number of commits
python manage.py branch_commits my-app --limit=10

# JSON output
python manage.py branch_commits my-app --format=json

Build Commands

build_list - List All Builds
# List all builds
python manage.py build_list

# Filter by repository
python manage.py build_list --repository=my-app

# Filter by status
python manage.py build_list --status=success

# Limit results
python manage.py build_list --limit=10

# JSON output
python manage.py build_list --format=json

Status options: pending, running, success, failed, cancelled

build_create - Create a New Build
# Build latest commit on default branch
python manage.py build_create my-app

# Build specific branch
python manage.py build_create my-app --branch=develop

# Build specific commit
python manage.py build_create my-app --commit=abc123

# Build and push to registry
python manage.py build_create my-app --push-to-registry

# Build with custom container port
python manage.py build_create my-app --container-port=3000

# Run build in background (don't wait)
python manage.py build_create my-app --no-wait

# Custom Dockerfile content
python manage.py build_create my-app --dockerfile-content="FROM node:18\nCOPY . .\nCMD ['npm', 'start']"

Options:

  • --branch: Branch name (defaults to repository default branch)
  • --commit: Commit SHA (defaults to latest commit on branch)
  • --push-to-registry: Push the built image to the registry
  • --container-port: Container port to expose (default: 8080)
  • --dockerfile-content: Custom Dockerfile content (inline)
  • --dockerfile-path: Path to Dockerfile in repo
  • --no-wait: Do not wait for build to complete (run in background)
  • --format: Output format (table or json)
build_detail - Get Build Details
# Get build details
python manage.py build_detail 42

# Include build logs
python manage.py build_detail 42 --show-logs

# JSON output
python manage.py build_detail 42 --format=json

Container Commands

container_list - List All Containers
# List all containers (successful builds)
python manage.py container_list

# List only running containers
python manage.py container_list --running-only

# JSON output
python manage.py container_list --format=json
container_start - Start a Container
# Start container for a build
python manage.py container_start 42

# Start with specific host port
python manage.py container_start 42 --host-port=8080

# JSON output
python manage.py container_start 42 --format=json
container_stop - Stop a Container
# Stop and remove container
python manage.py container_stop 42

# Stop but don't remove container
python manage.py container_stop 42 --no-remove

# JSON output
python manage.py container_stop 42 --format=json
container_logs - Get Container Logs
# Get container logs
python manage.py container_logs 42

# Get last 50 lines
python manage.py container_logs 42 --tail=50

# JSON output
python manage.py container_logs 42 --format=json

Complete CLI Workflow Example

Here's a complete example showing how to use the CLI commands for a full build and deploy workflow:

#!/bin/bash
# Complete CLI workflow example

# 1. Connect a repository
python manage.py repo_connect my-app https://github.com/user/my-app.git \
    --description="My Application" \
    --default-branch=main

# 2. Refresh branches to fetch from Git
python manage.py repo_refresh my-app

# 3. List commits on the main branch
python manage.py branch_commits my-app --branch=main --refresh

# 4. Create a build (waits for completion by default)
python manage.py build_create my-app --branch=main

# 5. Check build status
python manage.py build_detail 1

# 6. Start a container for the successful build
python manage.py container_start 1

# 7. View container logs
python manage.py container_logs 1 --tail=100

# 8. List all running containers
python manage.py container_list --running-only

# 9. Stop the container when done
python manage.py container_stop 1

JSON Output for Scripting

All CLI commands support JSON output format, making them ideal for scripting and automation:

# Get repository list as JSON
repos=$(python manage.py repo_list --format=json)

# Parse with jq
repo_id=$(echo "$repos" | jq -r '.[0].id')

# Create build and get result as JSON
result=$(python manage.py build_create "$repo_id" --format=json)
build_id=$(echo "$result" | jq -r '.id')
build_status=$(echo "$result" | jq -r '.status')

if [ "$build_status" = "success" ]; then
    echo "Build succeeded!"
    python manage.py container_start "$build_id"
fi

πŸš€ Advanced Use Cases

Use Case 1: Multi-Environment Deployment Pipeline

Scenario: You have three environments (dev, staging, production) with different branches.

Setup:

  1. Configure Repository with Multiple Branches

    # Add repository via API or Admin
    # Branches: develop, staging, main
  2. Create Deployment Script

    #!/bin/bash
    # deploy.sh
    
    ENVIRONMENT=$1  # dev, staging, or prod
    NOHANDS_URL="http://nohands.internal.company.com"
    
    case $ENVIRONMENT in
      dev)
        BRANCH="develop"
        REGISTRY="dev-registry.company.com"
        ;;
      staging)
        BRANCH="staging"
        REGISTRY="staging-registry.company.com"
        ;;
      prod)
        BRANCH="main"
        REGISTRY="prod-registry.company.com"
        ;;
      *)
        echo "Invalid environment"
        exit 1
        ;;
    esac
    
    # Get latest commit from branch
    COMMIT_ID=$(curl -s "${NOHANDS_URL}/api/commits/?branch=${BRANCH}&limit=1" | \
      jq -r '.results[0].id')
    
    # Trigger build
    curl -X POST "${NOHANDS_URL}/api/builds/trigger/" \
      -H "Content-Type: application/json" \
      -d "{
        \"repository_id\": 1,
        \"commit_id\": ${COMMIT_ID},
        \"push_to_registry\": true
      }"
  3. Use in CI/CD

    # .gitlab-ci.yml or similar
    deploy_dev:
      script: ./deploy.sh dev
      only:
        - develop
    
    deploy_staging:
      script: ./deploy.sh staging
      only:
        - staging
    
    deploy_prod:
      script: ./deploy.sh prod
      only:
        - main
      when: manual

Use Case 2: Building Multiple Microservices

Scenario: You have a monorepo with multiple services, each with its own Dockerfile.

Setup:

  1. Add Multiple Repositories (one per service)

    # Via Django admin or API
    repositories = [
        {
            "name": "api-service",
            "url": "/path/to/monorepo",
            "dockerfile_path": "services/api/Dockerfile"
        },
        {
            "name": "worker-service",
            "url": "/path/to/monorepo",
            "dockerfile_path": "services/worker/Dockerfile"
        },
        {
            "name": "frontend-service",
            "url": "/path/to/monorepo",
            "dockerfile_path": "services/frontend/Dockerfile"
        }
    ]
  2. Build All Services Script

    #!/bin/bash
    # build_all_services.sh
    
    SERVICES=("api-service" "worker-service" "frontend-service")
    COMMIT_SHA=$1
    
    for SERVICE in "${SERVICES[@]}"; do
      echo "Building ${SERVICE}..."
      
      # Get repository ID
      REPO_ID=$(curl -s "http://localhost:8000/api/repositories/" | \
        jq -r ".results[] | select(.name==\"${SERVICE}\") | .id")
      
      # Get commit ID
      COMMIT_ID=$(curl -s "http://localhost:8000/api/commits/?repository=${REPO_ID}" | \
        jq -r ".results[] | select(.sha==\"${COMMIT_SHA}\") | .id")
      
      # Trigger build
      BUILD_ID=$(curl -s -X POST "http://localhost:8000/api/builds/trigger/" \
        -H "Content-Type: application/json" \
        -d "{
          \"repository_id\": ${REPO_ID},
          \"commit_id\": ${COMMIT_ID},
          \"push_to_registry\": true
        }" | jq -r '.id')
      
      echo "Build triggered for ${SERVICE}: #${BUILD_ID}"
    done

Use Case 3: Automated Rollback

Scenario: Automatically rollback to the previous working build if deployment fails.

Implementation:

#!/usr/bin/env python3
# rollback.py

import requests
import sys

class DeploymentManager:
    def __init__(self, nohands_url):
        self.base_url = nohands_url.rstrip('/')
    
    def get_successful_builds(self, repository_id, limit=10):
        """Get recent successful builds."""
        url = f"{self.base_url}/api/builds/"
        params = {
            'repository': repository_id,
            'status': 'success',
            'ordering': '-completed_at'
        }
        response = requests.get(url, params=params)
        return response.json()['results'][:limit]
    
    def trigger_rollback(self, repository_id):
        """Rollback to previous successful build."""
        builds = self.get_successful_builds(repository_id, limit=2)
        
        if len(builds) < 2:
            print("Not enough successful builds for rollback")
            return None
        
        previous_build = builds[1]  # Second most recent
        commit_id = previous_build['commit_id']
        
        print(f"Rolling back to commit: {previous_build['commit_sha']}")
        
        # Trigger new build with previous commit
        url = f"{self.base_url}/api/builds/trigger/"
        data = {
            'repository_id': repository_id,
            'commit_id': commit_id,
            'push_to_registry': True
        }
        response = requests.post(url, json=data)
        return response.json()

# Usage
manager = DeploymentManager("http://localhost:8000")
rollback_build = manager.trigger_rollback(repository_id=1)
print(f"Rollback build triggered: #{rollback_build['id']}")

Use Case 4: Build Status Notifications

Scenario: Send notifications when builds complete or fail.

Implementation:

#!/usr/bin/env python3
# build_monitor.py

import requests
import time
import smtplib
from email.mime.text import MIMEText

class BuildMonitor:
    def __init__(self, nohands_url, smtp_config):
        self.base_url = nohands_url.rstrip('/')
        self.smtp_config = smtp_config
        self.seen_builds = set()
    
    def send_notification(self, build):
        """Send email notification."""
        subject = f"Build #{build['id']} {build['status']}: {build['repository_name']}"
        body = f"""
        Build Details:
        - Repository: {build['repository_name']}
        - Commit: {build['commit_sha']}
        - Branch: {build['branch_name']}
        - Status: {build['status']}
        - Duration: {build['duration']}
        
        Image Tag: {build['image_tag']}
        
        Error: {build['error_message']}
        
        View build: {self.base_url}/builds/{build['id']}/
        """
        
        msg = MIMEText(body)
        msg['Subject'] = subject
        msg['From'] = self.smtp_config['from']
        msg['To'] = self.smtp_config['to']
        
        with smtplib.SMTP(self.smtp_config['host'], self.smtp_config['port']) as server:
            server.send_message(msg)
    
    def monitor(self, interval=30):
        """Monitor builds and send notifications."""
        while True:
            # Get recent builds
            url = f"{self.base_url}/api/builds/"
            response = requests.get(url, params={'ordering': '-created_at'})
            builds = response.json()['results']
            
            for build in builds:
                build_id = build['id']
                status = build['status']
                
                # Check if build is complete and not seen
                if build_id not in self.seen_builds and status in ['success', 'failed']:
                    print(f"Build #{build_id} completed with status: {status}")
                    self.send_notification(build)
                    self.seen_builds.add(build_id)
            
            time.sleep(interval)

# Usage
smtp_config = {
    'host': 'smtp.example.com',
    'port': 587,
    'from': 'builds@example.com',
    'to': 'team@example.com'
}

monitor = BuildMonitor("http://localhost:8000", smtp_config)
monitor.monitor(interval=30)

Use Case 5: Scheduled Nightly Builds

Scenario: Build the latest commit from main branch every night.

Implementation using cron:

# Add to crontab: crontab -e
# Run nightly build at 2 AM
0 2 * * * /usr/local/bin/nightly_build.sh >> /var/log/nightly_builds.log 2>&1

nightly_build.sh:

#!/bin/bash
# nightly_build.sh

NOHANDS_URL="http://localhost:8000"
REPOSITORY_ID=1

# Get latest commit from main branch
LATEST_COMMIT=$(curl -s "${NOHANDS_URL}/api/commits/?repository=${REPOSITORY_ID}&branch=main&limit=1" | \
  jq -r '.results[0]')

COMMIT_ID=$(echo "$LATEST_COMMIT" | jq -r '.id')
COMMIT_SHA=$(echo "$LATEST_COMMIT" | jq -r '.sha')

echo "[$(date)] Starting nightly build for commit ${COMMIT_SHA}"

# Trigger build
BUILD_RESPONSE=$(curl -s -X POST "${NOHANDS_URL}/api/builds/trigger/" \
  -H "Content-Type: application/json" \
  -d "{
    \"repository_id\": ${REPOSITORY_ID},
    \"commit_id\": ${COMMIT_ID},
    \"push_to_registry\": true
  }")

BUILD_ID=$(echo "$BUILD_RESPONSE" | jq -r '.id')
echo "[$(date)] Build triggered: #${BUILD_ID}"

Use Case 6: Local Repository Builds

Scenario: Build from a local Git repository instead of remote.

Setup:

  1. Add Local Repository

    # Via Django admin
    Name: my-local-project
    URL: /home/user/projects/my-app
    Dockerfile Path: Dockerfile
  2. Build Process

    • NoHands will use the local path directly
    • No cloning needed, faster builds
    • Useful for development and testing
  3. Update Local Repo

    # Make changes
    cd /home/user/projects/my-app
    git commit -am "Update feature"
    
    # Refresh in NoHands
    curl -X POST "http://localhost:8000/refresh-branches/" # Via web UI
    
    # Trigger build
    # Use the web UI or API to build from the new commit

Git Operations

The system uses GitPython for Git operations:

  • Clone/Update: Repositories are cached locally for performance
  • Branch Listing: Fetches all branches from repository
  • Commit History: Lists commits with metadata
  • Checkout: Creates isolated checkouts for builds

Dagger Pipeline

The Dagger pipeline (builds/dagger_pipeline.py):

  1. Build Phase:

    • Loads source directory
    • Builds Docker image using Dockerfile
    • Tags with commit SHA
  2. Push Phase (optional):

    • Authenticates with registry
    • Pushes image with full tag
    • Returns image reference
  3. Output:

    • Status (success/failed)
    • Build logs
    • Image tag
    • Duration

πŸ“ Project Structure

NoHands/
β”œβ”€β”€ manage.py                      # Django management script
β”œβ”€β”€ requirements.txt               # Python dependencies
β”œβ”€β”€ Dockerfile.example             # Example Dockerfile for projects
β”œβ”€β”€ .gitignore                     # Git ignore rules
β”œβ”€β”€ README.md                      # This file
β”‚
β”œβ”€β”€ nohands_project/              # Django project settings
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ settings.py               # Main settings file
β”‚   β”œβ”€β”€ urls.py                   # Root URL configuration
β”‚   β”œβ”€β”€ wsgi.py                   # WSGI application
β”‚   └── asgi.py                   # ASGI application
β”‚
β”œβ”€β”€ projects/                     # Repository management app
β”‚   β”œβ”€β”€ models.py                 # GitRepository, Branch, Commit models
β”‚   β”œβ”€β”€ views.py                  # Web views for repositories
β”‚   β”œβ”€β”€ admin.py                  # Admin configuration
β”‚   β”œβ”€β”€ git_utils.py              # Git utilities (clone, list branches, etc.)
β”‚   β”œβ”€β”€ urls.py                   # App URL patterns
β”‚   β”œβ”€β”€ apps.py                   # App configuration
β”‚   β”œβ”€β”€ tests.py                  # Unit tests
β”‚   β”œβ”€β”€ migrations/               # Database migrations
β”‚   └── templates/
β”‚       β”œβ”€β”€ base.html             # Base template
β”‚       └── projects/
β”‚           β”œβ”€β”€ repository_list.html     # List repositories
β”‚           β”œβ”€β”€ repository_detail.html   # Repository details
β”‚           └── branch_commits.html      # Branch commits
β”‚
β”œβ”€β”€ builds/                       # Build management app
β”‚   β”œβ”€β”€ models.py                 # Build model
β”‚   β”œβ”€β”€ views.py                  # Build views and execution
β”‚   β”œβ”€β”€ admin.py                  # Admin configuration
β”‚   β”œβ”€β”€ dagger_pipeline.py        # Dagger build pipeline
β”‚   β”œβ”€β”€ urls.py                   # App URL patterns
β”‚   β”œβ”€β”€ apps.py                   # App configuration
β”‚   β”œβ”€β”€ tests.py                  # Unit tests
β”‚   β”œβ”€β”€ migrations/               # Database migrations
β”‚   └── templates/
β”‚       └── builds/
β”‚           β”œβ”€β”€ build_list.html          # List builds
β”‚           β”œβ”€β”€ build_detail.html        # Build details and logs
β”‚           └── build_create.html        # Create build form
β”‚
β”œβ”€β”€ api/                          # REST API app
β”‚   β”œβ”€β”€ views.py                  # API viewsets (repositories, builds, etc.)
β”‚   β”œβ”€β”€ serializers.py            # DRF serializers
β”‚   β”œβ”€β”€ urls.py                   # API URL patterns
β”‚   β”œβ”€β”€ apps.py                   # App configuration
β”‚   β”œβ”€β”€ admin.py                  # Admin configuration
β”‚   β”œβ”€β”€ tests.py                  # API tests
β”‚   └── migrations/               # Database migrations
β”‚
└── tmp/                          # Temporary files (auto-created, gitignored)
    └── git_checkouts/
        β”œβ”€β”€ cache/                # Cached repository clones
        β”‚   └── repo-name/       # One directory per repository
        └── builds/              # Temporary checkouts for builds
            └── build_123/       # One directory per build

Key Files Explained

  • manage.py: Django's command-line utility for administrative tasks
  • requirements.txt: Lists all Python package dependencies
  • settings.py: Django configuration (database, apps, middleware, etc.)
  • models.py: Database models defining data structure
  • views.py: Request handlers for web pages
  • serializers.py: Data serialization for REST API
  • git_utils.py: Reusable Git operations (clone, list branches, checkout)
  • dagger_pipeline.py: Docker build logic using Dagger SDK
  • templates/: HTML templates for web interface
  • migrations/: Database schema changes tracked by Django

πŸ‘₯ Contributing

We welcome contributions to NoHands! Here's how you can help:

Ways to Contribute

  1. Report Bugs: Open an issue describing the bug and how to reproduce it
  2. Suggest Features: Open an issue describing the feature and its use case
  3. Submit Pull Requests: Fix bugs or implement features
  4. Improve Documentation: Help make the docs better
  5. Write Tests: Add test coverage for existing features

Development Setup

  1. Fork and Clone:

    git clone https://github.com/your-username/NoHands.git
    cd NoHands
  2. Create Virtual Environment:

    python -m venv venv
    source venv/bin/activate  # or venv\Scripts\activate on Windows
  3. Install Dependencies:

    pip install -r requirements.txt
  4. Install Development Tools:

    pip install black flake8 pytest pytest-django
  5. Run Migrations:

    python manage.py migrate
  6. Create Superuser:

    python manage.py createsuperuser
  7. Run Development Server:

    python manage.py runserver

Coding Standards

  • Python Style: Follow PEP 8 guidelines
  • Formatting: Use black for code formatting
    black .
  • Linting: Use flake8 for linting
    flake8 .
  • Type Hints: Use type hints where appropriate
  • Docstrings: Add docstrings to functions and classes
  • Comments: Comment complex logic

Running Tests

# Run all tests
python manage.py test

# Run specific app tests
python manage.py test projects
python manage.py test builds
python manage.py test api

# Run with coverage
pip install coverage
coverage run --source='.' manage.py test
coverage report

Pull Request Process

  1. Create a Branch:

    git checkout -b feature/your-feature-name
    # or
    git checkout -b fix/your-bug-fix
  2. Make Changes:

    • Write code following the coding standards
    • Add tests for new features
    • Update documentation if needed
  3. Test Your Changes:

    python manage.py test
    python manage.py check
    black .
    flake8 .
  4. Commit with Clear Messages:

    git add .
    git commit -m "Add feature: description of feature"
    # or
    git commit -m "Fix: description of bug fix"
  5. Push to Your Fork:

    git push origin feature/your-feature-name
  6. Open Pull Request:

    • Go to GitHub and open a PR
    • Describe your changes clearly
    • Reference any related issues

Commit Message Guidelines

  • Use present tense ("Add feature" not "Added feature")
  • Use imperative mood ("Move cursor to..." not "Moves cursor to...")
  • First line should be concise (50 chars or less)
  • Add detailed description after blank line if needed

Examples:

Add support for multi-stage Dockerfiles

Implement parsing of multi-stage Dockerfile syntax and
update build pipeline to handle multiple build stages.

Fixes #123

Code Review Process

  1. Maintainers will review your PR
  2. Address any feedback or requested changes
  3. Once approved, your PR will be merged
  4. Your contribution will be credited in the release notes

Development Tips

  • Use Django Shell for Testing:

    python manage.py shell
  • Check Migrations:

    python manage.py makemigrations --dry-run
    python manage.py makemigrations
  • Run Individual Tests:

    python manage.py test projects.tests.TestGitUtils
  • Debug with IPython:

    pip install ipython
    # Use in code: import IPython; IPython.embed()

Project Structure

NoHands/
β”œβ”€β”€ manage.py
β”œβ”€β”€ requirements.txt
β”œβ”€β”€ Dockerfile.example
β”œβ”€β”€ nohands_project/          # Django project settings
β”‚   β”œβ”€β”€ settings.py
β”‚   β”œβ”€β”€ urls.py
β”‚   └── ...
β”œβ”€β”€ projects/                 # Repository management app
β”‚   β”œβ”€β”€ models.py             # GitRepository, Branch, Commit models
β”‚   β”œβ”€β”€ views.py              # Web views
β”‚   β”œβ”€β”€ admin.py              # Admin configuration
β”‚   β”œβ”€β”€ git_utils.py          # Git utilities
β”‚   β”œβ”€β”€ urls.py
β”‚   └── templates/
β”‚       └── projects/
β”œβ”€β”€ builds/                   # Build management app
β”‚   β”œβ”€β”€ models.py             # Build model
β”‚   β”œβ”€β”€ views.py              # Build views
β”‚   β”œβ”€β”€ admin.py
β”‚   β”œβ”€β”€ dagger_pipeline.py    # Dagger build pipeline
β”‚   β”œβ”€β”€ urls.py
β”‚   └── templates/
β”‚       └── builds/
└── api/                      # REST API app
    β”œβ”€β”€ views.py              # API viewsets
    β”œβ”€β”€ serializers.py        # DRF serializers
    └── urls.py

Best Practices

Security

  • Never commit sensitive credentials to Git
  • Use environment variables for registry credentials and SECRET_KEY
  • Set DJANGO_SECRET_KEY environment variable in production
  • Set DJANGO_DEBUG=False in production
  • Set DJANGO_ALLOWED_HOSTS to your domain(s) in production
  • Protect admin panel with strong passwords
  • Use HTTPS in production
  • Implement rate limiting for API endpoints

Performance

  • Repositories are cached to avoid repeated clones
  • Builds run in background threads (for production, consider using Celery)
  • Consider using Celery or Django-Q for production workloads
  • Clean up old build directories periodically

🏭 Production Deployment

Important Considerations

⚠️ WARNING: The current implementation uses background threads for build execution. For production deployments, you MUST use a proper task queue system like Celery or Django-Q for reliable background job processing.

Production Checklist

Before deploying to production, ensure you've completed:

  • Set DJANGO_DEBUG=False
  • Set a strong DJANGO_SECRET_KEY (minimum 50 characters)
  • Configure DJANGO_ALLOWED_HOSTS with your domain(s)
  • Switch from SQLite to PostgreSQL
  • Set up a task queue (Celery/Django-Q)
  • Configure a message broker (Redis/RabbitMQ)
  • Use a production WSGI server (Gunicorn/uWSGI)
  • Set up static file serving (WhiteNoise/CDN)
  • Configure Docker registry credentials securely
  • Set up SSL/TLS (HTTPS)
  • Configure log aggregation and monitoring
  • Set up automated backups
  • Implement rate limiting on API endpoints
  • Configure firewall rules

Quick Production Setup

For a complete production setup guide with Celery, Gunicorn, Nginx, PostgreSQL, and Docker Compose, see the DEPLOYMENT.md file (recommended for production environments).

Minimal Production Configuration

If you need a quick production setup, here are the essential changes:

1. Environment Variables:

export DJANGO_SECRET_KEY="your-long-random-secret-key-minimum-50-characters"
export DJANGO_DEBUG=False
export DJANGO_ALLOWED_HOSTS="yourdomain.com,www.yourdomain.com"
export DOCKER_REGISTRY="registry.example.com"
export DOCKER_REGISTRY_USERNAME="your-username"
export DOCKER_REGISTRY_PASSWORD="your-password"

2. Use PostgreSQL:

# Install PostgreSQL
sudo apt-get install postgresql

# Create database
sudo -u postgres createdb nohands
sudo -u postgres createuser nohands_user

# Update settings
export DB_NAME=nohands
export DB_USER=nohands_user
export DB_PASSWORD=secure_password

3. Use Gunicorn:

pip install gunicorn
gunicorn nohands_project.wsgi:application --bind 0.0.0.0:8000 --workers 4

Production Deployment

Important: The current implementation uses background threads for build execution. For production deployments, consider:

  1. Task Queue: Use Celery, Django-Q, or similar for reliable background job processing
  2. Message Broker: Redis or RabbitMQ for task queue
  3. Process Manager: Gunicorn or uWSGI instead of Django dev server
  4. Database: PostgreSQL instead of SQLite
  5. Static Files: Configure static file serving with WhiteNoise or CDN
  6. Monitoring: Add logging, error tracking (Sentry), and monitoring

Example production environment variables:

export DJANGO_SECRET_KEY="your-long-random-secret-key-here"
export DJANGO_DEBUG=False
export DJANGO_ALLOWED_HOSTS="yourdomain.com,www.yourdomain.com"
export DOCKER_REGISTRY="registry.example.com"
export DOCKER_REGISTRY_USERNAME="your-username"
export DOCKER_REGISTRY_PASSWORD="your-password"

πŸ”§ Troubleshooting

Common Issues and Solutions

Issue 1: Build Fails with "Git Error"

Symptoms:

  • Build status shows failed
  • Error message contains Git-related errors

Solutions:

  1. Check Repository URL:

    # Verify the URL is accessible
    git ls-remote <repository-url>
  2. For Private Repositories:

    • Use SSH URL with proper SSH keys configured
    • Or use HTTPS with credentials: https://username:token@github.com/user/repo.git
  3. For Local Repositories:

    • Ensure the path is absolute and accessible
    • Check file permissions
  4. Refresh Repository:

    • Go to repository detail page
    • Click "Refresh Branches" to re-sync

Issue 2: "Dagger Connection Failed"

Symptoms:

  • Build fails immediately
  • Error mentions Dagger or Docker connection

Solutions:

  1. Ensure Docker is Running:

    docker ps
    # Should list containers without errors
  2. Check Docker Socket Permissions:

    sudo usermod -aG docker $USER
    # Log out and back in for changes to take effect
  3. Verify Dagger Installation:

    python -c "import dagger; print(dagger.__version__)"
  4. Check Docker Version:

    docker --version
    # Should be 20.x or higher

Issue 3: "Registry Push Failed"

Symptoms:

  • Build succeeds but push fails
  • Error mentions authentication or registry

Solutions:

  1. Verify Registry Credentials:

    echo $DOCKER_REGISTRY_USERNAME
    echo $DOCKER_REGISTRY_PASSWORD  # (be careful not to expose)
  2. Test Registry Login Manually:

    docker login $DOCKER_REGISTRY -u $DOCKER_REGISTRY_USERNAME -p $DOCKER_REGISTRY_PASSWORD
  3. Check Registry URL Format:

    • Should NOT include http:// or https://
    • Example: registry.example.com not https://registry.example.com
  4. Verify Network Connectivity:

    ping registry.example.com
    curl -I https://registry.example.com

Issue 4: "Module Not Found" After Installation

Symptoms:

  • Import errors when running Django commands
  • Missing dependency errors

Solutions:

  1. Activate Virtual Environment:

    source venv/bin/activate  # Linux/macOS
    venv\Scripts\activate      # Windows
  2. Reinstall Dependencies:

    pip install -r requirements.txt --force-reinstall
  3. Check Python Version:

    python --version  # Should be 3.11+

Issue 5: "Permission Denied" Errors

Symptoms:

  • Cannot clone repositories
  • Cannot create build directories
  • Cannot write to database

Solutions:

  1. Check Directory Permissions:

    ls -la /path/to/NoHands/tmp
    # Ensure current user has write permissions
  2. Create Directories with Correct Permissions:

    mkdir -p tmp/git_checkouts/cache
    mkdir -p tmp/git_checkouts/builds
    chmod -R 755 tmp/
  3. For Database:

    # SQLite
    chmod 664 db.sqlite3
    
    # PostgreSQL - check connection permissions
    sudo -u postgres psql -c "\du"

Issue 6: Builds Stuck in "Pending" Status

Symptoms:

  • Builds never start
  • Status remains "pending" indefinitely

Solutions:

  1. Check for Exceptions:

    # View Django logs
    python manage.py runserver  # Check console output
    
    # Or check log files if configured
    tail -f /var/log/nohands/django.log
  2. Verify Threading/Celery:

    • If using threads: Ensure no exceptions in the thread
    • If using Celery: Check Celery worker is running
      celery -A nohands_project inspect active
  3. Check Database Locks:

    # For SQLite, check if file is locked
    lsof db.sqlite3
  4. Restart Services:

    # Development
    # Just restart the Django server
    
    # Production with systemd
    sudo systemctl restart nohands nohands-celery

Issue 7: "Dockerfile Not Found"

Symptoms:

  • Build fails with "Dockerfile not found"
  • Error mentions missing Dockerfile

Solutions:

  1. Verify Dockerfile Path in Repository Config:

    • Check the dockerfile_path field in repository settings
    • Default is Dockerfile (case-sensitive!)
  2. Check Dockerfile Location:

    # Clone the repo locally and verify
    git clone <repository-url> test-clone
    cd test-clone
    ls -la Dockerfile  # Or the configured path
  3. Common Paths:

    • Root: Dockerfile
    • Docker directory: docker/Dockerfile
    • Build directory: build/Dockerfile
  4. Update Repository Configuration:

    • Admin β†’ Git Repositories β†’ Select repo β†’ Edit
    • Update Dockerfile Path field

Issue 8: High Resource Usage

Symptoms:

  • System slows down during builds
  • High CPU or memory usage

Solutions:

  1. Limit Concurrent Builds:

    export MAX_CONCURRENT_BUILDS=1
  2. Monitor Resources:

    # CPU and Memory
    top
    
    # Docker resources
    docker stats
  3. Clean Up Old Builds:

    # Remove old build directories
    find tmp/git_checkouts/builds -type d -mtime +7 -exec rm -rf {} \;
    
    # Clean Docker images
    docker image prune -a
  4. Configure Docker Resource Limits:

    # Edit Docker daemon config
    sudo vim /etc/docker/daemon.json
    {
      "max-concurrent-downloads": 3,
      "max-concurrent-uploads": 3
    }

Issue 9: API Returns 404 or 500 Errors

Symptoms:

  • API endpoints return errors
  • Cannot access API routes

Solutions:

  1. Verify URL Pattern:

    # List all URLs
    python manage.py show_urls  # If django-extensions installed
    
    # Or check urls.py files
    cat nohands_project/urls.py
    cat api/urls.py
  2. Check Allowed Hosts:

    # settings.py
    ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'your-domain.com']
  3. Enable Debug for Development:

    export DJANGO_DEBUG=True
    # Restart server to see detailed error messages
  4. Check Django Logs:

    # In runserver output or log files
    tail -f /var/log/nohands/django.log

Getting Help

If you encounter issues not covered here:

  1. Check Django Logs:

    • Development: Check console output
    • Production: Check log files
  2. Enable Debug Mode (Development Only):

    export DJANGO_DEBUG=True
    python manage.py runserver
  3. Run Django Check:

    python manage.py check
    python manage.py check --deploy  # Production checks
  4. Test Components Individually:

    # Test Git operations
    python manage.py shell
    >>> from projects.git_utils import clone_or_update_repo
    >>> from pathlib import Path
    >>> clone_or_update_repo("https://github.com/user/repo.git", Path("/tmp/test"))
    
    # Test Dagger
    >>> from builds.dagger_pipeline import run_build_sync
    >>> # Test build with small project
  5. Check GitHub Issues:

Development

  • Use Django's check command: python manage.py check
  • Run tests: python manage.py test
  • Use migrations for database changes
  • Follow Django best practices

πŸ“‹ Example Dockerfile

If your project doesn't have a Dockerfile, use Dockerfile.example as a template:

# Example Dockerfile for a Python application
FROM python:3.11-slim

WORKDIR /app

# Install system dependencies if needed
# RUN apt-get update && apt-get install -y some-package

# Copy requirements and install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code
COPY . .

# Expose port (adjust as needed)
EXPOSE 8000

# Run the application
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

Dockerfile Best Practices

  1. Use Specific Base Image Tags:

    FROM python:3.11-slim  # Good
    FROM python:latest     # Avoid
  2. Minimize Layers:

    # Good - single RUN command
    RUN apt-get update && apt-get install -y \
        package1 \
        package2 \
        && rm -rf /var/lib/apt/lists/*
    
    # Avoid - multiple RUN commands
    RUN apt-get update
    RUN apt-get install -y package1
    RUN apt-get install -y package2
  3. Use .dockerignore:

    # .dockerignore
    __pycache__
    *.pyc
    *.pyo
    *.pyd
    .Python
    env/
    venv/
    .venv
    .git
    .gitignore
    .env
    *.sqlite3
    *.log
    node_modules/
    
  4. Multi-stage Builds (for compiled apps):

    # Build stage
    FROM node:18 AS builder
    WORKDIR /app
    COPY package*.json ./
    RUN npm ci
    COPY . .
    RUN npm run build
    
    # Production stage
    FROM nginx:alpine
    COPY --from=builder /app/dist /usr/share/nginx/html
    EXPOSE 80
    CMD ["nginx", "-g", "daemon off;"]

πŸ› Troubleshooting

Build Fails with Git Error

  • Ensure the repository URL is accessible
  • Check Git credentials if using private repos
  • Verify Git is installed: git --version

Dagger Connection Failed

  • Ensure Docker is running
  • Check Dagger installation: dagger version
  • Review Dagger logs in build output

Registry Push Failed

  • Verify registry credentials
  • Ensure registry URL is correct
  • Check network connectivity

πŸ“š Additional Resources

Documentation

Related Projects

Tutorials and Guides

🀝 Community

Getting Help

Contributing

We welcome contributions! See the Contributing section above for guidelines.

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Submit a pull request

πŸ“œ License

This project is open source and available under the MIT License.

MIT License

Copyright (c) 2025 NoHands Contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

πŸ™ Acknowledgments

NoHands is built with the following amazing open-source projects:

  • Django - The web framework for perfectionists with deadlines
  • Django REST Framework - Powerful and flexible toolkit for building Web APIs
  • Dagger - Application delivery as code
  • GitPython - Python library for interacting with Git repositories
  • Docker - Platform for developing, shipping, and running applications

πŸ“ž Support

For issues and questions:

Reporting Security Issues

If you discover a security vulnerability, please email the maintainers directly instead of opening a public issue. We take security seriously and will respond promptly.

πŸ—ΊοΈ Roadmap

Future enhancements and features planned for NoHands:

Short Term (Next Release)

  • WebSocket support for real-time build logs
  • Build queue management and prioritization
  • Enhanced error reporting and debugging tools
  • Docker Compose file support
  • Build artifacts management

Medium Term

  • Celery integration for background tasks (production-ready)
  • Multi-registry support (push to multiple registries)
  • Build notifications (email, Slack, webhooks)
  • Advanced build configurations (environment variables, build args)
  • Build templates and presets

Long Term

  • Kubernetes deployment support
  • Build caching and optimization
  • Scheduled builds and recurring builds
  • Build comparison and diffing
  • Integration with CI/CD platforms (GitHub Actions, GitLab CI)
  • Web-based Dockerfile editor
  • Build analytics and metrics dashboard
  • Multi-user support with role-based access control
  • Build approval workflows

Want to Help?

Check out our Contributing section to get started with any of these features!

πŸ“Š Project Stats

  • Language: Python 3.11+
  • Framework: Django 5.0+
  • License: MIT
  • Status: Active Development

🌟 Star History

If you find NoHands useful, please consider giving it a star on GitHub! ⭐


Made with ❀️ by the NoHands community

For more information, visit: https://github.com/vbrunelle/NoHands

About

Django-based web interface for automating Docker image builds and deployments from Git repositories

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors