diff --git a/Makefile b/Makefile index fee9fcf..feb5475 100644 --- a/Makefile +++ b/Makefile @@ -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" diff --git a/README.md b/README.md index 583b96e..8b3d02d 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 diff --git a/docker/Dockerfile b/docker/Dockerfile index 7984e0d..92f18ff 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -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"] diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 8118e55..eed0854 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -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 diff --git a/docker/template.env b/docker/template.env index 8d2cd85..1b7ed75 100644 --- a/docker/template.env +++ b/docker/template.env @@ -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 diff --git a/docs/container-setup.md b/docs/container-setup.md new file mode 100644 index 0000000..58d4f72 --- /dev/null +++ b/docs/container-setup.md @@ -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! +``` \ No newline at end of file