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.
- 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
- Architecture
- Prerequisites
- Installation
- Docker Deployment
- Quick Start
- Configuration
- Usage Examples
- Advanced Use Cases
- Production Deployment
- Troubleshooting
- Contributing
- License
NoHands is built on Django 5+ and uses a modular architecture with three main applications:
-
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
-
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 metadataBranch: Branch tracking and latest commit infoCommit: 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
Before installing NoHands, ensure you have the following installed:
-
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
- 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
- Virtual Environment: Use
venvorvirtualenvto isolate dependencies - PostgreSQL: For production deployments (SQLite is used by default for development)
- Redis: For task queues in production (if using Celery)
- Python 3.11+
- Django 5+
- Dagger (installed via Python SDK)
- Git
- Docker (for Dagger builds)
git clone https://github.com/vbrunelle/NoHands.git
cd NoHands# Create virtual environment
python -m venv venv
# Activate virtual environment
# On Linux/macOS:
source venv/bin/activate
# On Windows:
venv\Scripts\activatepip install -r requirements.txtThis will install:
- Django 5.0+
- Django REST Framework
- Dagger SDK (for Docker builds)
- GitPython (for Git operations)
- And other required dependencies
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=2python manage.py migrateThis creates the SQLite database and all necessary tables.
Important: GitHub OAuth authentication is required for users to connect and manage repositories.
-
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
-
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_oauthThe setup command will automatically configure the OAuth application in the database.
See GITHUB_OAUTH.md for detailed instructions.
While users can authenticate via GitHub, you may want to create a Django admin account:
python manage.py createsuperuserFollow the prompts to create an admin account:
- Username: (e.g., admin)
- Email: your-email@example.com
- Password: (enter a secure password)
Note: The first user to connect via GitHub automatically becomes a superuser.
python manage.py runserverThe server will start at http://localhost:8000/
Open your browser and navigate to:
- Web Interface: http://localhost:8000/
- Admin Panel: http://localhost:8000/admin/
- API Root: http://localhost:8000/api/
Log in with the superuser credentials you created.
NoHands can be deployed using Docker for easy containerization and deployment.
-
Build the Docker image:
docker build -t nohands . -
Run the container:
docker run -p 8000:8000 nohands
-
Access the application:
- Web Interface: http://localhost:8000/
- Admin Panel: http://localhost:8000/admin/
- API Root: http://localhost:8000/api/
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" \
nohandsTo 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" \
nohandsTo 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" \
nohandsCreate 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| 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 |
For production Docker deployments:
-
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"]
-
Use PostgreSQL: Configure a PostgreSQL database instead of SQLite.
-
Set up a reverse proxy: Use Nginx or Traefik in front of the application.
-
Enable HTTPS: Configure SSL/TLS certificates.
-
Set proper secrets: Always use strong, unique secret keys in production.
NoHands images are automatically built and published to GitHub Container Registry when changes are pushed to the main branch or when tags are created.
# 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-abc1234docker run -p 8000:8000 \
-e DJANGO_SECRET_KEY="your-secret-key" \
-v /var/run/docker.sock:/var/run/docker.sock \
ghcr.io/vbrunelle/nohands:mainA 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 downOptionally, 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.
If the repository is private, the GHCR package will be private by default. To use private images:
-
Generate a Personal Access Token (PAT):
- Go to https://github.com/settings/tokens
- Click "Generate new token (classic)"
- Select scope:
read:packages - Copy the generated token
-
Login to GHCR:
echo $GITHUB_PAT | docker login ghcr.io -u YOUR_USERNAME --password-stdin
-
Pull the private image:
docker pull ghcr.io/vbrunelle/nohands:main
To manage the package visibility:
- Navigate to the repository's Packages section
- Click on the package (e.g.,
nohands) - Go to Package settings
- 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_TOKENwhich has automaticpackages:writepermission. No additional secrets configuration is needed for the CI/CD pipeline.
Let's walk through building your first Docker image with NoHands!
- Go to the Admin Panel: http://localhost:8000/admin/
- Click on Git Repositories β Add Git Repository
- Fill in the form:
- Name:
my-project - URL:
https://github.com/username/my-project.gitor a local path like/path/to/repo - Description:
My first project - Default Branch:
main(ormaster) - Dockerfile Path:
Dockerfile(path to Dockerfile in your repo) - Is Active: β checked
- Name:
- Click Save
- Go to the main page: http://localhost:8000/
- Click on your repository name
- Click Refresh Branches button
- Wait for branches to be fetched from Git
- Click on any branch name (e.g.,
main) - Click Refresh Commits to fetch latest commits
- You'll see a list of commits with their messages, authors, and timestamps
- In the commits list, click the π Build button next to any commit
- Configure build options:
- Push to Registry: Check if you want to push to a Docker registry (requires registry configuration)
- Leave unchecked for local builds
- Click Start Build
- You'll be redirected to the build detail page
- The status will change from
pendingβrunningβsuccessorfailed - View real-time logs as the build progresses
- Check the build duration and image tag when complete
That's it! You've successfully built your first Docker image with NoHands.
- Clone the repository:
git clone https://github.com/vbrunelle/NoHands.git
cd NoHands- Install dependencies:
pip install -r requirements.txt- Run migrations:
python manage.py migrate- Create superuser (for admin access):
python manage.py createsuperuser- Run the development server:
python manage.py runserver- Access the application:
- Web UI: http://localhost:8000/
- Admin: http://localhost:8000/admin/
- API: http://localhost:8000/api/
NoHands can be configured using environment variables. Create a .env file or set them in your shell:
# 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"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"# Maximum concurrent builds (default: 1)
# Increase if you have sufficient resources
MAX_CONCURRENT_BUILDS=3You can also modify nohands_project/settings.py directly:
# Directory for temporary Git checkouts
GIT_CHECKOUT_DIR = BASE_DIR / 'tmp' / 'git_checkouts'This directory will contain:
cache/: Cached repository clonesbuilds/: Temporary checkouts for each build
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 = {
'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',
],
}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 |
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.
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=1Key settings in nohands_project/settings.py:
GIT_CHECKOUT_DIR: Directory for temporary Git checkoutsDOCKER_REGISTRY: Docker registry URLMAX_CONCURRENT_BUILDS: Maximum concurrent build jobs
Use Case: You want to build a Docker image from a specific commit in your repository.
-
Navigate to Repository
- Go to http://localhost:8000/
- Click on your repository name
-
Select Branch
- Click on the branch containing your commit (e.g.,
main) - If you don't see branches, click Refresh Branches first
- Click on the branch containing your commit (e.g.,
-
Find Your Commit
- Browse the commit list or click Refresh Commits to get the latest
- Identify your commit by message, author, or timestamp
-
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
-
Monitor Progress
- View build status in real-time
- Check logs for detailed output
- Note the image tag when complete (e.g.,
my-app:a1b2c3d4)
Use Case: Build an image and push it to Docker Hub or a private registry.
-
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
- Set environment variables:
-
Trigger Build with Push
- Follow steps from Workflow 1
- When configuring the build, check β Push to Registry
- Click Start Build
-
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
Use Case: You maintain multiple environments (dev, staging, prod) in different branches.
-
Add Repository (if not already added)
- Admin β Git Repositories β Add
- Configure as shown in Quick Start
-
Refresh Branches
- Navigate to repository detail
- Click Refresh Branches
- All branches will be synced
-
Build from Each Branch
- Click on
developbranch β Refresh Commits β Build from latest - Click on
stagingbranch β Refresh Commits β Build from latest - Click on
mainbranch β Refresh Commits β Build from latest
- Click on
-
Track All Builds
- Go to http://localhost:8000/builds/
- See all builds across all repositories and branches
- Filter by status: pending, running, success, failed
Use Case: Review past builds and their results.
-
View All Builds
- Navigate to http://localhost:8000/builds/
- See a chronological list of all builds
-
Filter Builds
- Builds are listed with:
- Build ID
- Repository name
- Commit SHA
- Branch name
- Status (pending/running/success/failed)
- Created timestamp
- Builds are listed with:
-
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
-
Analyze Failures
- Check error messages in failed builds
- Review logs to debug issues
- Identify patterns in failures
The REST API enables full automation and integration with CI/CD pipelines.
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"
}
]
}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"
}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"
}
]
}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"
}
]
}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"
}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"
}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"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
doneimport 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']}")-
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.gitor local path
-
Browse Branches:
- View repository details
- Click "Refresh Branches" to fetch from Git
- Select a branch to view commits
-
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
-
Monitor Builds:
- View all builds at
/builds/ - Click a build to see detailed logs
- Builds run asynchronously in background threads
- View all builds at
curl http://localhost:8000/api/repositories/curl http://localhost:8000/api/branches/?repository=1curl http://localhost:8000/api/commits/?branch=1curl http://localhost:8000/api/builds/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
}'curl http://localhost:8000/api/builds/1/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.
# 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=jsonOutput 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
# 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=adminOptions:
--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
# 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# 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# 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=jsonStatus options: pending, running, success, failed, cancelled
# 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)
# 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# 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# 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# 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# 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=jsonHere'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 1All 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"
fiScenario: You have three environments (dev, staging, production) with different branches.
Setup:
-
Configure Repository with Multiple Branches
# Add repository via API or Admin # Branches: develop, staging, main
-
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 }"
-
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
Scenario: You have a monorepo with multiple services, each with its own Dockerfile.
Setup:
-
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" } ]
-
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
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']}")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)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>&1nightly_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}"Scenario: Build from a local Git repository instead of remote.
Setup:
-
Add Local Repository
# Via Django admin Name: my-local-project URL: /home/user/projects/my-app Dockerfile Path: Dockerfile
-
Build Process
- NoHands will use the local path directly
- No cloning needed, faster builds
- Useful for development and testing
-
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
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
The Dagger pipeline (builds/dagger_pipeline.py):
-
Build Phase:
- Loads source directory
- Builds Docker image using Dockerfile
- Tags with commit SHA
-
Push Phase (optional):
- Authenticates with registry
- Pushes image with full tag
- Returns image reference
-
Output:
- Status (success/failed)
- Build logs
- Image tag
- Duration
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
- 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
We welcome contributions to NoHands! Here's how you can help:
- Report Bugs: Open an issue describing the bug and how to reproduce it
- Suggest Features: Open an issue describing the feature and its use case
- Submit Pull Requests: Fix bugs or implement features
- Improve Documentation: Help make the docs better
- Write Tests: Add test coverage for existing features
-
Fork and Clone:
git clone https://github.com/your-username/NoHands.git cd NoHands -
Create Virtual Environment:
python -m venv venv source venv/bin/activate # or venv\Scripts\activate on Windows
-
Install Dependencies:
pip install -r requirements.txt
-
Install Development Tools:
pip install black flake8 pytest pytest-django
-
Run Migrations:
python manage.py migrate
-
Create Superuser:
python manage.py createsuperuser
-
Run Development Server:
python manage.py runserver
- Python Style: Follow PEP 8 guidelines
- Formatting: Use
blackfor code formattingblack . - Linting: Use
flake8for lintingflake8 . - Type Hints: Use type hints where appropriate
- Docstrings: Add docstrings to functions and classes
- Comments: Comment complex logic
# 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-
Create a Branch:
git checkout -b feature/your-feature-name # or git checkout -b fix/your-bug-fix -
Make Changes:
- Write code following the coding standards
- Add tests for new features
- Update documentation if needed
-
Test Your Changes:
python manage.py test python manage.py check black . flake8 .
-
Commit with Clear Messages:
git add . git commit -m "Add feature: description of feature" # or git commit -m "Fix: description of bug fix"
-
Push to Your Fork:
git push origin feature/your-feature-name
-
Open Pull Request:
- Go to GitHub and open a PR
- Describe your changes clearly
- Reference any related issues
- 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
- Maintainers will review your PR
- Address any feedback or requested changes
- Once approved, your PR will be merged
- Your contribution will be credited in the release notes
-
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()
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
- Never commit sensitive credentials to Git
- Use environment variables for registry credentials and SECRET_KEY
- Set
DJANGO_SECRET_KEYenvironment variable in production - Set
DJANGO_DEBUG=Falsein production - Set
DJANGO_ALLOWED_HOSTSto your domain(s) in production - Protect admin panel with strong passwords
- Use HTTPS in production
- Implement rate limiting for API endpoints
- 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
Before deploying to production, ensure you've completed:
- Set
DJANGO_DEBUG=False - Set a strong
DJANGO_SECRET_KEY(minimum 50 characters) - Configure
DJANGO_ALLOWED_HOSTSwith 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
For a complete production setup guide with Celery, Gunicorn, Nginx, PostgreSQL, and Docker Compose, see the DEPLOYMENT.md file (recommended for production environments).
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_password3. Use Gunicorn:
pip install gunicorn
gunicorn nohands_project.wsgi:application --bind 0.0.0.0:8000 --workers 4Important: The current implementation uses background threads for build execution. For production deployments, consider:
- Task Queue: Use Celery, Django-Q, or similar for reliable background job processing
- Message Broker: Redis or RabbitMQ for task queue
- Process Manager: Gunicorn or uWSGI instead of Django dev server
- Database: PostgreSQL instead of SQLite
- Static Files: Configure static file serving with WhiteNoise or CDN
- 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"Symptoms:
- Build status shows
failed - Error message contains Git-related errors
Solutions:
-
Check Repository URL:
# Verify the URL is accessible git ls-remote <repository-url>
-
For Private Repositories:
- Use SSH URL with proper SSH keys configured
- Or use HTTPS with credentials:
https://username:token@github.com/user/repo.git
-
For Local Repositories:
- Ensure the path is absolute and accessible
- Check file permissions
-
Refresh Repository:
- Go to repository detail page
- Click "Refresh Branches" to re-sync
Symptoms:
- Build fails immediately
- Error mentions Dagger or Docker connection
Solutions:
-
Ensure Docker is Running:
docker ps # Should list containers without errors -
Check Docker Socket Permissions:
sudo usermod -aG docker $USER # Log out and back in for changes to take effect
-
Verify Dagger Installation:
python -c "import dagger; print(dagger.__version__)" -
Check Docker Version:
docker --version # Should be 20.x or higher
Symptoms:
- Build succeeds but push fails
- Error mentions authentication or registry
Solutions:
-
Verify Registry Credentials:
echo $DOCKER_REGISTRY_USERNAME echo $DOCKER_REGISTRY_PASSWORD # (be careful not to expose)
-
Test Registry Login Manually:
docker login $DOCKER_REGISTRY -u $DOCKER_REGISTRY_USERNAME -p $DOCKER_REGISTRY_PASSWORD
-
Check Registry URL Format:
- Should NOT include
http://orhttps:// - Example:
registry.example.comnothttps://registry.example.com
- Should NOT include
-
Verify Network Connectivity:
ping registry.example.com curl -I https://registry.example.com
Symptoms:
- Import errors when running Django commands
- Missing dependency errors
Solutions:
-
Activate Virtual Environment:
source venv/bin/activate # Linux/macOS venv\Scripts\activate # Windows
-
Reinstall Dependencies:
pip install -r requirements.txt --force-reinstall
-
Check Python Version:
python --version # Should be 3.11+
Symptoms:
- Cannot clone repositories
- Cannot create build directories
- Cannot write to database
Solutions:
-
Check Directory Permissions:
ls -la /path/to/NoHands/tmp # Ensure current user has write permissions -
Create Directories with Correct Permissions:
mkdir -p tmp/git_checkouts/cache mkdir -p tmp/git_checkouts/builds chmod -R 755 tmp/
-
For Database:
# SQLite chmod 664 db.sqlite3 # PostgreSQL - check connection permissions sudo -u postgres psql -c "\du"
Symptoms:
- Builds never start
- Status remains "pending" indefinitely
Solutions:
-
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
-
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
-
Check Database Locks:
# For SQLite, check if file is locked lsof db.sqlite3 -
Restart Services:
# Development # Just restart the Django server # Production with systemd sudo systemctl restart nohands nohands-celery
Symptoms:
- Build fails with "Dockerfile not found"
- Error mentions missing Dockerfile
Solutions:
-
Verify Dockerfile Path in Repository Config:
- Check the
dockerfile_pathfield in repository settings - Default is
Dockerfile(case-sensitive!)
- Check the
-
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
-
Common Paths:
- Root:
Dockerfile - Docker directory:
docker/Dockerfile - Build directory:
build/Dockerfile
- Root:
-
Update Repository Configuration:
- Admin β Git Repositories β Select repo β Edit
- Update
Dockerfile Pathfield
Symptoms:
- System slows down during builds
- High CPU or memory usage
Solutions:
-
Limit Concurrent Builds:
export MAX_CONCURRENT_BUILDS=1 -
Monitor Resources:
# CPU and Memory top # Docker resources docker stats
-
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
-
Configure Docker Resource Limits:
# Edit Docker daemon config sudo vim /etc/docker/daemon.json{ "max-concurrent-downloads": 3, "max-concurrent-uploads": 3 }
Symptoms:
- API endpoints return errors
- Cannot access API routes
Solutions:
-
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
-
Check Allowed Hosts:
# settings.py ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'your-domain.com']
-
Enable Debug for Development:
export DJANGO_DEBUG=True # Restart server to see detailed error messages
-
Check Django Logs:
# In runserver output or log files tail -f /var/log/nohands/django.log
If you encounter issues not covered here:
-
Check Django Logs:
- Development: Check console output
- Production: Check log files
-
Enable Debug Mode (Development Only):
export DJANGO_DEBUG=True python manage.py runserver -
Run Django Check:
python manage.py check python manage.py check --deploy # Production checks -
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
-
Check GitHub Issues:
- Visit: https://github.com/vbrunelle/NoHands/issues
- Search for similar problems
- Open a new issue with:
- Detailed error messages
- Steps to reproduce
- Environment information (OS, Python version, Docker version)
- Use Django's
checkcommand:python manage.py check - Run tests:
python manage.py test - Use migrations for database changes
- Follow Django best practices
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"]-
Use Specific Base Image Tags:
FROM python:3.11-slim # Good FROM python:latest # Avoid
-
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
-
Use .dockerignore:
# .dockerignore __pycache__ *.pyc *.pyo *.pyd .Python env/ venv/ .venv .git .gitignore .env *.sqlite3 *.log node_modules/ -
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;"]
- Ensure the repository URL is accessible
- Check Git credentials if using private repos
- Verify Git is installed:
git --version
- Ensure Docker is running
- Check Dagger installation:
dagger version - Review Dagger logs in build output
- Verify registry credentials
- Ensure registry URL is correct
- Check network connectivity
- Django Documentation: https://docs.djangoproject.com/
- Django REST Framework: https://www.django-rest-framework.org/
- Dagger Documentation: https://docs.dagger.io/
- GitPython Documentation: https://gitpython.readthedocs.io/
- Docker Documentation: https://docs.docker.com/
- Dagger: https://dagger.io/ - Application delivery as code
- Jenkins: https://www.jenkins.io/ - Automation server
- GitLab CI/CD: https://docs.gitlab.com/ee/ci/ - GitLab's built-in CI/CD
- GitHub Actions: https://github.com/features/actions - GitHub's workflow automation
- ArgoCD: https://argo-cd.readthedocs.io/ - GitOps continuous delivery
- Django for Beginners: https://djangoforbeginners.com/
- Docker Tutorial: https://docs.docker.com/get-started/
- REST API Design: https://restfulapi.net/
- Git Basics: https://git-scm.com/book/en/v2/Getting-Started-Git-Basics
- GitHub Issues: https://github.com/vbrunelle/NoHands/issues
- Discussions: Use GitHub Discussions for questions and ideas
- Documentation: Refer to this README and code comments
We welcome contributions! See the Contributing section above for guidelines.
- Fork the repository
- Create a feature branch
- Make your changes
- Submit a pull request
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.
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
For issues and questions:
- GitHub Issues: https://github.com/vbrunelle/NoHands/issues
- Documentation: Refer to this comprehensive README
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.
Future enhancements and features planned for NoHands:
- 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
- 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
- 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
Check out our Contributing section to get started with any of these features!
- Language: Python 3.11+
- Framework: Django 5.0+
- License: MIT
- Status: Active Development
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