Complete guide for deploying applications generated from Trobz Python Template.
- Using the Template
- CLI Development
- Service Development
- Service Deployment
- Configuration for Deployment
- GitHub Actions Configuration
- Troubleshooting
Before using the template, ensure you have:
- Python 3.12+ (for running the template generator)
- Copier 8.0+ installed (
pip install copier) - Git for version control
- uv package manager (automatically installed by generated Makefile)
Use Copier to generate a project from the template:
# Generate from GitHub (recommended)
copier copy "https://github.com/trobz/trobz-python-template.git" ./my-project
# Or from local checkout
copier copy path/to/template ./my-projectAnswer the prompts to customize your project:
project_name? [my-project]: My Application
package_name? [my_application]:
project_description? A brief description of your app
project_type? [cli]:
1. cli
2. service
> 2
service_framework? [fastapi]:
1. fastapi
2. flask
> 1
repository_namespace? [trobz]: myorg
repository_name? [my-application]:
author_username? [username]: john-doe
author_email? [john@example.com]:
enable_github_action? [False]: True
publish_to_pypi? [False]: True
After generation, initialize the project:
cd my-project
# Install dependencies and setup hooks
make install
# Verify installation
make check
make testGenerated CLI projects have this structure:
my-cli/
├── my_cli/
│ ├── __init__.py
│ └── main.py # Typer CLI application
├── tests/
│ └── test_main.py
├── Makefile
├── pyproject.toml
├── ruff.toml
├── ty.toml
└── README.md
- Add Commands: Extend
main.pywith new commands
import typer
app = typer.Typer()
@app.command()
def hello(name: str = typer.Argument("World")):
"""Say hello to someone."""
typer.echo(f"Hello {name}")
@app.command()
def goodbye(name: str):
"""Say goodbye to someone."""
typer.echo(f"Goodbye {name}")- Test Locally: Run commands via uv
uv run my-cli hello Alice
uv run my-cli goodbye Bob- Add Dependencies
uv add requests
uv add --dev pytest-cov- Build Distribution
make build
# Creates dist/my_cli-*.whl# Build the wheel
make build
# Install on target system
pip install dist/my_cli-1.0.0-py3-none-any.whl
# Use the CLI
my-cli hello World# On target system
git clone https://github.com/myorg/my-cli.git
cd my-cli
make install
# Use via uv
uv run my-cli hello World# Configure PyPI credentials
# See "GitHub Actions Configuration" section
# Tag for release
git tag v1.0.0
git push origin v1.0.0
# GitHub Actions automatically publishes to PyPI
# Users install with pip
pip install my-cliGenerated service projects include:
my-api/
├── my_api/
│ ├── __init__.py
│ ├── main.py # FastAPI/Flask application
│ └── settings.py # Pydantic settings
├── tests/
│ └── test_main.py
├── .env.sample # Configuration template
├── Makefile
├── pyproject.toml
└── README.md
- Setup Environment
make install
# Copy environment template
cp .env.sample .env.local
# Edit configuration
ENVIRONMENT=development
DEBUG=true
SERVER_HOST=0.0.0.0
SERVER_PORT=8000- Run Development Server
# FastAPI (with auto-reload)
make serve
# Or manually
uv run uvicorn my_api.main:app --reload
# Access at http://localhost:8000- Add Routes (FastAPI example)
from fastapi import FastAPI
from my_api.settings import get_settings
app = FastAPI()
settings = get_settings()
@app.get("/")
async def root():
return {"message": "Hello World", "env": settings.environment}
@app.get("/health")
async def health():
return {"status": "ok"}
@app.get("/users/{user_id}")
async def get_user(user_id: int):
return {"user_id": user_id, "name": "John Doe"}- Test API
# Health check
curl http://localhost:8000/health
# View docs
open http://localhost:8000/docs
# Run tests
make testChoose deployment method based on requirements:
| Method | Use Case | Complexity | Scalability |
|---|---|---|---|
| Uvicorn (Direct) | Development, testing | Low | Low |
| Gunicorn + Uvicorn | Production, single server | Medium | Medium |
| Docker | Containerized deployment | Medium | High |
| Docker Compose | Multi-service apps | Medium | Medium |
| Kubernetes | Cloud-native, high scale | High | Very High |
| Platform Services | Quick deployment | Low | High |
Best for: Production single-server deployments
# Install gunicorn
uv add gunicorn
# Run with workers
gunicorn my_api.main:app \
--workers 4 \
--worker-class uvicorn.workers.UvicornWorker \
--bind 0.0.0.0:8000 \
--access-logfile - \
--error-logfile -Create systemd service (/etc/systemd/system/my-api.service):
[Unit]
Description=My API Service
After=network.target
[Service]
Type=notify
User=www-data
Group=www-data
WorkingDirectory=/opt/my-api
Environment="PATH=/opt/my-api/.venv/bin"
ExecStart=/opt/my-api/.venv/bin/gunicorn my_api.main:app \
--workers 4 \
--worker-class uvicorn.workers.UvicornWorker \
--bind 0.0.0.0:8000
Restart=always
[Install]
WantedBy=multi-user.targetEnable and start:
sudo systemctl daemon-reload
sudo systemctl enable my-api
sudo systemctl start my-api
sudo systemctl status my-apiService projects use .env files for configuration:
# .env.local (not tracked, highest priority)
ENVIRONMENT=production
DEBUG=false
SERVER_HOST=0.0.0.0
SERVER_PORT=8000
# Database
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
# API Keys
API_SECRET_KEY=your-secret-key-here
EXTERNAL_API_KEY=external-service-keySettings are loaded in this order (highest priority first):
.env.local(local overrides, not committed).env(tracked, can be committed for defaults)- Environment variables (system-level)
- Defaults in
settings.py
Example settings.py:
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
environment: str = "development"
debug: bool = False
server_host: str = "0.0.0.0"
server_port: int = 8000
# Database
database_url: str = "sqlite:///./app.db"
# Security
api_secret_key: str = "dev-secret-key"
model_config = SettingsConfigDict(
env_file=('.env.local', '.env'),
env_file_encoding='utf-8',
extra='ignore'
)
def get_settings() -> Settings:
return Settings()Never commit sensitive data:
# .gitignore
.env.local
.env.production
*.key
*.pem
secrets/Use environment-specific files:
# Development
.env.localRotate secrets regularly:
# Generate new secret key
python -c "import secrets; print(secrets.token_urlsafe(32))"When generating project, set enable_github_action = true to include workflows.
Runs on pull requests and main branch pushes:
# .github/workflows/test.yaml
name: Test
on:
pull_request:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12', '3.13']
steps:
- uses: actions/checkout@v6
- name: Setup Python Environment
uses: ./.github/actions/setup-python-env
with:
python-version: ${{ matrix.python-version }}
- name: Run tests
run: make testRuns linting and formatting checks:
# .github/workflows/pre-commit.yaml
name: Pre-commit
on:
pull_request:
push:
branches: [main]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Setup Python Environment
uses: ./.github/actions/setup-python-env
- name: Run pre-commit
run: make checkPrerequisites:
- Create PyPI account at https://pypi.org
- Configure Trusted Publishing in PyPI project settings
- Add repository secrets in GitHub
Configure Trusted Publishing (recommended):
- Go to PyPI project settings
- Add GitHub publisher
- Enter:
repository_namespace/repository_name - Workflow:
release.yaml
Or use API token (legacy):
- Generate PyPI API token
- Add to GitHub secrets:
PYPI_TOKEN
Trigger release:
# Commit with conventional format
git commit -m "feat: add new feature"
git push
# Semantic release creates tag automatically
# Or manually tag:
git tag v1.0.0
git push origin v1.0.0The template includes a reusable action for environment setup:
# .github/actions/setup-python-env/action.yaml
name: Setup Python Environment
description: Setup Python with uv and install dependencies
inputs:
python-version:
description: Python version to use
required: false
default: '3.12'
runs:
using: composite
steps:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ${{ inputs.python-version }}
- name: Install uv
shell: bash
run: pip install uv
- name: Install dependencies
shell: bash
run: uv syncIssue: Copier command fails with "template not found"
Solution:
# Verify Copier installation
copier --version # Should be 8.0+
# Use full HTTPS URL
copier copy "https://github.com/trobz/trobz-python-template.git" ./my-project
# Or clone first
git clone https://github.com/trobz/trobz-python-template.git
copier copy ./trobz-python-template ./my-projectIssue: make install fails with "uv: command not found"
Solution:
# Install uv globally
pip install uv
# Or use with pipx
pipx install uv
# Verify installation
uv --versionIssue: Commits rejected by pre-commit hooks
Solution:
# Run checks manually
make check
# Fix formatting issues
uv run ruff format .
# Fix linting issues
uv run ruff check --fix .
# Update hooks
uv run pre-commit autoupdateIssue: make test fails with import errors
Solution:
# Reinstall dependencies
uv sync --refresh
# Clear cache
uv cache clean
# Run tests with verbose output
uv run pytest -v
# Check Python version
python --version # Should be 3.10+Issue: make serve fails with port already in use
Solution:
# Check what's using the port
lsof -i :8000
# Kill process
kill -9 <PID>
# Or use different port
SERVER_PORT=8001 make serveIssue: CI/CD workflows fail on GitHub
Solution:
# Check workflow syntax locally
# Install act: https://github.com/nektos/act
act -l
# Test workflow locally
act push
# Check secrets are set
gh secret list
# View workflow logs
gh run view --logIssue: Release workflow fails to publish to PyPI
Solution:
# Verify PYPI_TOKEN secret exists
gh secret list | grep PYPI_TOKEN
# Test build locally
make build
ls dist/
# Verify token has correct permissions
# Go to PyPI → Account Settings → API tokens
# Check trusted publisher configuration
# PyPI project → Settings → PublishingIf you encounter issues not covered here:
-
Check Documentation:
-
Search Issues:
-
Ask for Help:
- GitHub Discussions: https://github.com/trobz/trobz-python-template/discussions
- Create new issue: https://github.com/trobz/trobz-python-template/issues/new
-
Community Resources:
- Copier docs: https://copier.readthedocs.io/
- uv docs: https://docs.astral.sh/uv/
- FastAPI docs: https://fastapi.tiangolo.com/
- Typer docs: https://typer.tiangolo.com/