Skip to content

Commit 7eb0cc5

Browse files
authored
Add Podman/Docker compatibility to container commands (#7)
* feat: add Podman/Docker compatibility to container commands - Auto-detects container engine (Podman preferred if available) - Handles UID/GID mapping for both engines - Updates all container-related Make targets - Adds container-info target to show current configuration - Includes troubleshooting documentation Users can now use either Docker or Podman seamlessly, with the ability to override via CONTAINER_ENGINE environment variable. * fix(podman): improve macOS compatibility and machine management - Fix Podman machine detection with proper JSON parsing - Add automatic machine startup when needed - Use podman-compose instead of podman compose - Add comprehensive Podman readiness checks - Update documentation with macOS-specific troubleshooting Resolves issues with Podman setup on macOS where the machine needs to be running and podman-compose must be used for compose functionality. * fix(podman): resolve volume mount and permission issues - Fix Dockerfile to use --system flag for uv pip sync - Simplify docker-compose.yml volume mounts to avoid missing files - Use :Z option for proper SELinux context in Podman - Remove user mapping to let Podman rootless handle permissions - Mount entire project as /workspace for simplicity Podman dev environment now works correctly on macOS. * fix(docker): make container setup compatible with make init - Remove hardcoded src/ directory references from Dockerfile - Use /workspace as WORKDIR to match volume mount location - Copy dependencies to /tmp for installation, then clean up - This ensures containers work both before and after make init renames src/ The container setup now survives project initialization and directory restructuring. * docs: add section on make init compatibility for containers
1 parent 176dc88 commit 7eb0cc5

File tree

6 files changed

+235
-56
lines changed

6 files changed

+235
-56
lines changed

Makefile

Lines changed: 68 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -120,31 +120,81 @@ clean-example: # Remove example code (use this to start your own project)
120120
init: setup # Initialize a new project
121121
uv run python scripts/init_project.py
122122

123-
# Docker
124-
########
123+
# Container Engine Support
124+
########################
125+
# Auto-detect container engine (podman or docker)
126+
CONTAINER_ENGINE ?= $(shell command -v podman >/dev/null 2>&1 && echo podman || echo docker)
127+
128+
# Podman-specific adjustments
129+
ifeq ($(CONTAINER_ENGINE),podman)
130+
# Use podman-compose for compose functionality
131+
COMPOSE_CMD = podman-compose
132+
# Use host UID/GID for rootless containers
133+
CONTAINER_USER_OPTS = --userns=keep-id
134+
# Podman machine status check
135+
PODMAN_MACHINE_RUNNING = $(shell podman machine list --format json 2>/dev/null | grep '"Running": true' >/dev/null && echo yes || echo no)
136+
else
137+
# Docker: use native compose
138+
COMPOSE_CMD = $(CONTAINER_ENGINE) compose
139+
# Docker: use current user's UID/GID to avoid permission issues
140+
CONTAINER_USER_OPTS = --user $(shell id -u):$(shell id -g)
141+
endif
142+
143+
# Docker/Podman Images
144+
#####################
125145
IMAGE_NAME = container-registry.io/python-collab-template
126146
IMAGE_TAG = latest
127147

128-
dev-env: refresh-containers
129-
@echo "Spinning up a dev environment ."
130-
@docker compose -f docker/docker-compose.yml down
131-
@docker compose -f docker/docker-compose.yml up -d dev
132-
@docker exec -ti composed_dev /bin/bash
148+
dev-env: ensure-container-ready refresh-containers
149+
@echo "Spinning up a dev environment using $(CONTAINER_ENGINE)..."
150+
@$(COMPOSE_CMD) -f docker/docker-compose.yml down
151+
@$(COMPOSE_CMD) -f docker/docker-compose.yml up -d dev
152+
@$(CONTAINER_ENGINE) exec -ti composed_dev /bin/bash
133153

134-
refresh-containers:
135-
@echo "Rebuilding containers..."
136-
@docker compose -f docker/docker-compose.yml build
154+
refresh-containers: ensure-container-ready
155+
@echo "Rebuilding containers using $(CONTAINER_ENGINE)..."
156+
@$(COMPOSE_CMD) -f docker/docker-compose.yml build
137157

138158
rebuild-images:
139-
@echo "Rebuilding images with the --no-cache flag..."
140-
@docker compose -f docker/docker-compose.yml build --no-cache
159+
@echo "Rebuilding images with the --no-cache flag using $(CONTAINER_ENGINE)..."
160+
@$(COMPOSE_CMD) -f docker/docker-compose.yml build --no-cache
141161

142162
build-image:
143-
@echo Building dev image and tagging as ${IMAGE_NAME}:${IMAGE_TAG}
144-
@docker compose -f docker/docker-compose.yml down
145-
@docker compose -f docker/docker-compose.yml up -d dev
146-
@docker tag dev ${IMAGE_NAME}:${IMAGE_TAG}
163+
@echo Building dev image using $(CONTAINER_ENGINE) and tagging as ${IMAGE_NAME}:${IMAGE_TAG}
164+
@$(COMPOSE_CMD) -f docker/docker-compose.yml down
165+
@$(COMPOSE_CMD) -f docker/docker-compose.yml up -d dev
166+
@$(CONTAINER_ENGINE) tag dev ${IMAGE_NAME}:${IMAGE_TAG}
147167

148168
push-image: build-image
149-
@echo Pushing image to container registry
150-
@docker push ${IMAGE_NAME}:${IMAGE_TAG}
169+
@echo Pushing image to container registry using $(CONTAINER_ENGINE)
170+
@$(CONTAINER_ENGINE) push ${IMAGE_NAME}:${IMAGE_TAG}
171+
172+
# Container Engine Info
173+
######################
174+
ensure-container-ready: # Ensure container engine is ready
175+
ifeq ($(CONTAINER_ENGINE),podman)
176+
@echo "Checking Podman machine status..."
177+
@if [ "$(PODMAN_MACHINE_RUNNING)" = "no" ]; then \
178+
echo "Podman machine is not running. Starting it..."; \
179+
podman machine start; \
180+
echo "Waiting for Podman machine to be ready..."; \
181+
sleep 3; \
182+
fi
183+
@if ! command -v podman-compose >/dev/null 2>&1; then \
184+
echo "Error: podman-compose not found. Install with: brew install podman-compose"; \
185+
exit 1; \
186+
fi
187+
else
188+
@echo "Using Docker engine..."
189+
endif
190+
191+
container-info: # Display detected container engine and configuration
192+
@echo "Container Engine: $(CONTAINER_ENGINE)"
193+
@echo "Compose Command: $(COMPOSE_CMD)"
194+
@echo "User Options: $(CONTAINER_USER_OPTS)"
195+
ifeq ($(CONTAINER_ENGINE),podman)
196+
@echo "Podman Machine Running: $(PODMAN_MACHINE_RUNNING)"
197+
@echo "podman-compose Available: $(shell command -v podman-compose >/dev/null 2>&1 && echo yes || echo no)"
198+
endif
199+
@echo ""
200+
@echo "To override, use: CONTAINER_ENGINE=podman make dev-env"

README.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,17 +87,28 @@ To remove the example code and start fresh:
8787
```bash
8888
make clean-example
8989
```
90-
## Docker Support
90+
## Container Support (Docker/Podman)
9191

9292
### Development Environment
93+
94+
The project automatically detects and uses either Docker or Podman:
95+
9396
```bash
94-
make dev-env # Start a development container
97+
make dev-env # Uses podman if available, otherwise docker
98+
99+
# Or explicitly choose:
100+
CONTAINER_ENGINE=docker make dev-env
101+
CONTAINER_ENGINE=podman make dev-env
102+
103+
# Check which engine will be used:
104+
make container-info
95105
```
96106

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

102113
### Production Image
103114
```bash
@@ -110,7 +121,7 @@ make push-image # Push to container registry
110121
.
111122
├── src/ # Source code
112123
├── tests/ # Test files
113-
├── docker/ # Docker configuration
124+
├── docker/ # Container configuration (Docker/Podman)
114125
├── .github/ # GitHub Actions workflows
115126
├── pyproject.toml # Project configuration
116127
└── Makefile # Development commands

docker/Dockerfile

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,17 @@ RUN apt-get update && apt-get install -y \
1414
vim \
1515
&& rm -rf /var/lib/apt/lists/*
1616

17-
WORKDIR /src
17+
WORKDIR /workspace
1818

19-
COPY pyproject.toml .
20-
COPY README.md .
21-
COPY src/ ./src/
22-
COPY tests/ ./tests/
19+
# Copy essential files for dependency resolution to temp location
20+
COPY pyproject.toml /tmp/
21+
COPY README.md /tmp/
2322

2423
RUN pip install -U pip uv \
24+
&& cd /tmp \
2525
&& uv pip compile pyproject.toml -o requirements.txt \
2626
&& uv pip compile pyproject.toml --extra dev -o requirements-dev.txt \
27-
&& uv pip sync requirements.txt requirements-dev.txt
27+
&& uv pip sync --system requirements.txt requirements-dev.txt \
28+
&& rm -rf /tmp/pyproject.toml /tmp/README.md
2829

2930
CMD ["/bin/bash"]

docker/docker-compose.yml

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,33 +11,7 @@ services:
1111
EXAMPLE: ${EXAMPLE}
1212
EXAMPLE_SECRET: ${EXAMPLE_SECRET}
1313
command: /bin/bash
14-
# Bind to local files
14+
# Note: user mapping handled by Podman rootless automatically
15+
# Bind whole project directory for simplicity
1516
volumes:
16-
- type: bind
17-
source: ../src
18-
target: /src/src
19-
read_only: False
20-
- type: bind
21-
source: ../tests
22-
target: /src/tests
23-
read_only: False
24-
- type: bind
25-
source: /var/run/docker.sock
26-
target: /var/run/docker.sock
27-
# Dev files in top level directory
28-
- type: bind
29-
source: ../docker/Makefile
30-
target: /src/Makefile
31-
read_only: False
32-
- type: bind
33-
source: ../mypy.ini
34-
target: /src/mypy.ini
35-
read_only: False
36-
- type: bind
37-
source: ../pylintrc
38-
target: /src/pylintrc
39-
read_only: False
40-
- type: bind
41-
source: ../README.md
42-
target: /src/README.md
43-
read_only: False
17+
- ../:/workspace:Z

docker/template.env

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,8 @@ DEBUG=true
88
# Secrets (do not commit actual values)
99
API_KEY=
1010
DATABASE_URL=
11+
12+
# Container runtime settings (optional)
13+
# UID=${UID} # Uncomment to use your system UID
14+
# GID=${GID} # Uncomment to use your system GID
15+
# DOCKER_SOCK=/var/run/docker.sock # Override socket path for Podman

docs/container-setup.md

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# Container Setup Guide
2+
3+
This project supports both Docker and Podman for containerized development.
4+
5+
## Quick Start
6+
7+
The Makefile automatically detects your container engine:
8+
9+
```bash
10+
# Automatic detection (prefers Podman if available)
11+
make dev-env
12+
13+
# Explicit engine selection
14+
CONTAINER_ENGINE=docker make dev-env
15+
CONTAINER_ENGINE=podman make dev-env
16+
```
17+
18+
## Podman vs Docker
19+
20+
### Key Differences
21+
22+
| Feature | Docker | Podman |
23+
|---------|--------|--------|
24+
| Root privileges | Runs as root by default | Rootless by default |
25+
| Daemon | Requires dockerd daemon | Daemonless |
26+
| Security | Good with proper setup | Better default security |
27+
| Compose support | Native | Via podman-compose |
28+
29+
### When to Use Which
30+
31+
**Use Docker when:**
32+
- It's your team's standard
33+
- You need Docker Desktop features
34+
- You're using Docker-specific tooling
35+
36+
**Use Podman when:**
37+
- Security is a top priority
38+
- You can't/don't want to run a daemon
39+
- You're in a restricted environment
40+
41+
## Troubleshooting
42+
43+
### Permission Issues
44+
45+
If you encounter permission issues with mounted volumes:
46+
47+
1. **For Podman**: Should work automatically with rootless mode
48+
2. **For Docker**: Set your UID/GID in `docker/.env`:
49+
```bash
50+
echo "UID=$(id -u)" >> docker/.env
51+
echo "GID=$(id -g)" >> docker/.env
52+
```
53+
54+
### Socket Issues
55+
56+
If Podman can't find the Docker socket:
57+
58+
```bash
59+
# Set the socket path in your .env
60+
echo "DOCKER_SOCK=${XDG_RUNTIME_DIR}/podman/podman.sock" >> docker/.env
61+
```
62+
63+
### Compose Command Not Found
64+
65+
For Podman, you need to install podman-compose:
66+
67+
```bash
68+
# macOS
69+
brew install podman-compose
70+
71+
# Linux
72+
pip install podman-compose
73+
```
74+
75+
### Podman Machine Not Running (macOS/Windows)
76+
77+
Podman needs a Linux VM to run containers. The Makefile will automatically start it, but you can also manage it manually:
78+
79+
```bash
80+
# Initialize a new machine
81+
podman machine init
82+
83+
# Start the machine
84+
podman machine start
85+
86+
# Check machine status
87+
podman machine list
88+
89+
# Stop the machine
90+
podman machine stop
91+
```
92+
93+
### Auto-Setup with Make
94+
95+
The project's Makefile handles most Podman setup automatically:
96+
97+
- Checks if Podman machine is running
98+
- Starts it if needed
99+
- Verifies podman-compose is installed
100+
- Uses appropriate socket paths
101+
102+
Just run `make container-info` to see the current status.
103+
104+
## Compatibility with Project Initialization
105+
106+
The container setup is designed to work both before and after running `make init`:
107+
108+
### Before `make init`
109+
- Source code is in `src/` directory
110+
- Container mounts entire project as `/workspace`
111+
- All development tools work normally
112+
113+
### After `make init`
114+
- Source code moves to your project module directory (e.g., `my_project/`)
115+
- Container setup continues to work unchanged
116+
- Volume mounts and dependencies remain intact
117+
118+
The `make init` command:
119+
1. Renames `src/` to your project name
120+
2. Updates import statements in tests
121+
3. Modifies `pyproject.toml` and `Makefile`
122+
123+
**The Docker/Podman setup survives this transformation** because:
124+
- The Dockerfile doesn't hardcode directory names
125+
- Dependencies are installed from temporary copied files
126+
- The entire project is mounted, regardless of internal structure
127+
128+
This means you can:
129+
```bash
130+
# Set up development environment
131+
make dev-env
132+
133+
# Initialize your project later
134+
make init
135+
136+
# Continue using the same development environment
137+
make dev-env # Still works!
138+
```

0 commit comments

Comments
 (0)