Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 68 additions & 18 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -120,31 +120,81 @@ clean-example: # Remove example code (use this to start your own project)
init: setup # Initialize a new project
uv run python scripts/init_project.py

# Docker
########
# Container Engine Support
########################
# Auto-detect container engine (podman or docker)
CONTAINER_ENGINE ?= $(shell command -v podman >/dev/null 2>&1 && echo podman || echo docker)

# Podman-specific adjustments
ifeq ($(CONTAINER_ENGINE),podman)
# Use podman-compose for compose functionality
COMPOSE_CMD = podman-compose
# Use host UID/GID for rootless containers
CONTAINER_USER_OPTS = --userns=keep-id
# Podman machine status check
PODMAN_MACHINE_RUNNING = $(shell podman machine list --format json 2>/dev/null | grep '"Running": true' >/dev/null && echo yes || echo no)
else
# Docker: use native compose
COMPOSE_CMD = $(CONTAINER_ENGINE) compose
# Docker: use current user's UID/GID to avoid permission issues
CONTAINER_USER_OPTS = --user $(shell id -u):$(shell id -g)
endif

# Docker/Podman Images
#####################
IMAGE_NAME = container-registry.io/python-collab-template
IMAGE_TAG = latest

dev-env: refresh-containers
@echo "Spinning up a dev environment ."
@docker compose -f docker/docker-compose.yml down
@docker compose -f docker/docker-compose.yml up -d dev
@docker exec -ti composed_dev /bin/bash
dev-env: ensure-container-ready refresh-containers
@echo "Spinning up a dev environment using $(CONTAINER_ENGINE)..."
@$(COMPOSE_CMD) -f docker/docker-compose.yml down
@$(COMPOSE_CMD) -f docker/docker-compose.yml up -d dev
@$(CONTAINER_ENGINE) exec -ti composed_dev /bin/bash

refresh-containers:
@echo "Rebuilding containers..."
@docker compose -f docker/docker-compose.yml build
refresh-containers: ensure-container-ready
@echo "Rebuilding containers using $(CONTAINER_ENGINE)..."
@$(COMPOSE_CMD) -f docker/docker-compose.yml build

rebuild-images:
@echo "Rebuilding images with the --no-cache flag..."
@docker compose -f docker/docker-compose.yml build --no-cache
@echo "Rebuilding images with the --no-cache flag using $(CONTAINER_ENGINE)..."
@$(COMPOSE_CMD) -f docker/docker-compose.yml build --no-cache

build-image:
@echo Building dev image and tagging as ${IMAGE_NAME}:${IMAGE_TAG}
@docker compose -f docker/docker-compose.yml down
@docker compose -f docker/docker-compose.yml up -d dev
@docker tag dev ${IMAGE_NAME}:${IMAGE_TAG}
@echo Building dev image using $(CONTAINER_ENGINE) and tagging as ${IMAGE_NAME}:${IMAGE_TAG}
@$(COMPOSE_CMD) -f docker/docker-compose.yml down
@$(COMPOSE_CMD) -f docker/docker-compose.yml up -d dev
@$(CONTAINER_ENGINE) tag dev ${IMAGE_NAME}:${IMAGE_TAG}

push-image: build-image
@echo Pushing image to container registry
@docker push ${IMAGE_NAME}:${IMAGE_TAG}
@echo Pushing image to container registry using $(CONTAINER_ENGINE)
@$(CONTAINER_ENGINE) push ${IMAGE_NAME}:${IMAGE_TAG}

# Container Engine Info
######################
ensure-container-ready: # Ensure container engine is ready
ifeq ($(CONTAINER_ENGINE),podman)
@echo "Checking Podman machine status..."
@if [ "$(PODMAN_MACHINE_RUNNING)" = "no" ]; then \
echo "Podman machine is not running. Starting it..."; \
podman machine start; \
echo "Waiting for Podman machine to be ready..."; \
sleep 3; \
fi
@if ! command -v podman-compose >/dev/null 2>&1; then \
echo "Error: podman-compose not found. Install with: brew install podman-compose"; \
exit 1; \
fi
else
@echo "Using Docker engine..."
endif

container-info: # Display detected container engine and configuration
@echo "Container Engine: $(CONTAINER_ENGINE)"
@echo "Compose Command: $(COMPOSE_CMD)"
@echo "User Options: $(CONTAINER_USER_OPTS)"
ifeq ($(CONTAINER_ENGINE),podman)
@echo "Podman Machine Running: $(PODMAN_MACHINE_RUNNING)"
@echo "podman-compose Available: $(shell command -v podman-compose >/dev/null 2>&1 && echo yes || echo no)"
endif
@echo ""
@echo "To override, use: CONTAINER_ENGINE=podman make dev-env"
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,28 @@ To remove the example code and start fresh:
```bash
make clean-example
```
## Docker Support
## Container Support (Docker/Podman)

### Development Environment

The project automatically detects and uses either Docker or Podman:

```bash
make dev-env # Start a development container
make dev-env # Uses podman if available, otherwise docker

# Or explicitly choose:
CONTAINER_ENGINE=docker make dev-env
CONTAINER_ENGINE=podman make dev-env

# Check which engine will be used:
make container-info
```

This creates a container with:
- All dependencies installed
- Source code mounted (changes reflect immediately)
- Development tools ready to use
- Automatic UID/GID mapping for file permissions

### Production Image
```bash
Expand All @@ -110,7 +121,7 @@ make push-image # Push to container registry
.
├── src/ # Source code
├── tests/ # Test files
├── docker/ # Docker configuration
├── docker/ # Container configuration (Docker/Podman)
├── .github/ # GitHub Actions workflows
├── pyproject.toml # Project configuration
└── Makefile # Development commands
Expand Down
13 changes: 7 additions & 6 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,17 @@ RUN apt-get update && apt-get install -y \
vim \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /src
WORKDIR /workspace

COPY pyproject.toml .
COPY README.md .
COPY src/ ./src/
COPY tests/ ./tests/
# Copy essential files for dependency resolution to temp location
COPY pyproject.toml /tmp/
COPY README.md /tmp/

RUN pip install -U pip uv \
&& cd /tmp \
&& uv pip compile pyproject.toml -o requirements.txt \
&& uv pip compile pyproject.toml --extra dev -o requirements-dev.txt \
&& uv pip sync requirements.txt requirements-dev.txt
&& uv pip sync --system requirements.txt requirements-dev.txt \
&& rm -rf /tmp/pyproject.toml /tmp/README.md

CMD ["/bin/bash"]
32 changes: 3 additions & 29 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,7 @@ services:
EXAMPLE: ${EXAMPLE}
EXAMPLE_SECRET: ${EXAMPLE_SECRET}
command: /bin/bash
# Bind to local files
# Note: user mapping handled by Podman rootless automatically
# Bind whole project directory for simplicity
volumes:
- type: bind
source: ../src
target: /src/src
read_only: False
- type: bind
source: ../tests
target: /src/tests
read_only: False
- type: bind
source: /var/run/docker.sock
target: /var/run/docker.sock
# Dev files in top level directory
- type: bind
source: ../docker/Makefile
target: /src/Makefile
read_only: False
- type: bind
source: ../mypy.ini
target: /src/mypy.ini
read_only: False
- type: bind
source: ../pylintrc
target: /src/pylintrc
read_only: False
- type: bind
source: ../README.md
target: /src/README.md
read_only: False
- ../:/workspace:Z
5 changes: 5 additions & 0 deletions docker/template.env
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@ DEBUG=true
# Secrets (do not commit actual values)
API_KEY=
DATABASE_URL=

# Container runtime settings (optional)
# UID=${UID} # Uncomment to use your system UID
# GID=${GID} # Uncomment to use your system GID
# DOCKER_SOCK=/var/run/docker.sock # Override socket path for Podman
138 changes: 138 additions & 0 deletions docs/container-setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Container Setup Guide

This project supports both Docker and Podman for containerized development.

## Quick Start

The Makefile automatically detects your container engine:

```bash
# Automatic detection (prefers Podman if available)
make dev-env

# Explicit engine selection
CONTAINER_ENGINE=docker make dev-env
CONTAINER_ENGINE=podman make dev-env
```

## Podman vs Docker

### Key Differences

| Feature | Docker | Podman |
|---------|--------|--------|
| Root privileges | Runs as root by default | Rootless by default |
| Daemon | Requires dockerd daemon | Daemonless |
| Security | Good with proper setup | Better default security |
| Compose support | Native | Via podman-compose |

### When to Use Which

**Use Docker when:**
- It's your team's standard
- You need Docker Desktop features
- You're using Docker-specific tooling

**Use Podman when:**
- Security is a top priority
- You can't/don't want to run a daemon
- You're in a restricted environment

## Troubleshooting

### Permission Issues

If you encounter permission issues with mounted volumes:

1. **For Podman**: Should work automatically with rootless mode
2. **For Docker**: Set your UID/GID in `docker/.env`:
```bash
echo "UID=$(id -u)" >> docker/.env
echo "GID=$(id -g)" >> docker/.env
```

### Socket Issues

If Podman can't find the Docker socket:

```bash
# Set the socket path in your .env
echo "DOCKER_SOCK=${XDG_RUNTIME_DIR}/podman/podman.sock" >> docker/.env
```

### Compose Command Not Found

For Podman, you need to install podman-compose:

```bash
# macOS
brew install podman-compose

# Linux
pip install podman-compose
```

### Podman Machine Not Running (macOS/Windows)

Podman needs a Linux VM to run containers. The Makefile will automatically start it, but you can also manage it manually:

```bash
# Initialize a new machine
podman machine init

# Start the machine
podman machine start

# Check machine status
podman machine list

# Stop the machine
podman machine stop
```

### Auto-Setup with Make

The project's Makefile handles most Podman setup automatically:

- Checks if Podman machine is running
- Starts it if needed
- Verifies podman-compose is installed
- Uses appropriate socket paths

Just run `make container-info` to see the current status.

## Compatibility with Project Initialization

The container setup is designed to work both before and after running `make init`:

### Before `make init`
- Source code is in `src/` directory
- Container mounts entire project as `/workspace`
- All development tools work normally

### After `make init`
- Source code moves to your project module directory (e.g., `my_project/`)
- Container setup continues to work unchanged
- Volume mounts and dependencies remain intact

The `make init` command:
1. Renames `src/` to your project name
2. Updates import statements in tests
3. Modifies `pyproject.toml` and `Makefile`

**The Docker/Podman setup survives this transformation** because:
- The Dockerfile doesn't hardcode directory names
- Dependencies are installed from temporary copied files
- The entire project is mounted, regardless of internal structure

This means you can:
```bash
# Set up development environment
make dev-env

# Initialize your project later
make init

# Continue using the same development environment
make dev-env # Still works!
```