Skip to content

Commit feef7d2

Browse files
committed
feat: add Podman support as drop-in replacement for Docker
1 parent 258542c commit feef7d2

16 files changed

Lines changed: 785 additions & 32 deletions

File tree

.github/workflows/kind-cats.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
--privileged \
3232
--pid host \
3333
-v /lib/modules:/lib/modules:ro \
34-
alpine sh -c "sysctl -w fs.inotify.max_user_instances=512 && modprobe nfs && modprobe nfsd"
34+
alpine sh -c "sysctl -w fs.inotify.max_user_instances=512 && sysctl -w net.ipv4.ip_unprivileged_port_start=80 && modprobe nfs && modprobe nfsd"
3535
- name: Install dependencies
3636
if: steps.check_changes.outputs.skip != 'true'
3737
run: |

.github/workflows/kind-smoke.yaml

Lines changed: 143 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,8 @@ jobs:
4242
- name: Set kernel settings
4343
if: steps.check_changes.outputs.skip != 'true'
4444
run: |
45-
docker run --rm \
46-
--privileged \
47-
--pid host \
48-
alpine sh -c "sysctl -w fs.inotify.max_user_instances=512"
45+
sudo sysctl -w fs.inotify.max_user_instances=512
46+
sudo sysctl -w net.ipv4.ip_unprivileged_port_start=80
4947
- name: Install dependencies
5048
if: steps.check_changes.outputs.skip != 'true'
5149
run: |
@@ -66,11 +64,14 @@ jobs:
6664
- name: Run make up (default)
6765
if: steps.check_changes.outputs.skip != 'true' && github.event_name == 'pull_request'
6866
run: make up
67+
env:
68+
CONTAINER_RUNTIME: docker
6969
- name: Run make up
7070
if: steps.check_changes.outputs.skip != 'true' && github.event_name != 'pull_request'
7171
run: make up
7272
env:
73-
INSTALL_OPTIONAL_COMPONENTS: ${{ inputs.minimal }}
73+
CONTAINER_RUNTIME: docker
74+
INSTALL_OPTIONAL_COMPONENTS: ${{ inputs.minimal }}
7475
- name: Login
7576
if: steps.check_changes.outputs.skip != 'true'
7677
run: make login
@@ -119,3 +120,140 @@ jobs:
119120
with:
120121
name: report.json
121122
path: ./cf-smoke-tests/report.json
123+
124+
kind-smoke-podman:
125+
runs-on: ubuntu-latest
126+
permissions:
127+
id-token: write
128+
contents: read
129+
steps:
130+
- uses: actions/checkout@v6
131+
with:
132+
fetch-depth: 0
133+
- name: Check if only ignored paths changed
134+
id: check_changes
135+
run: |
136+
if [ "${{ github.event_name }}" == "pull_request" ]; then
137+
CHANGED_FILES=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }})
138+
RELEVANT_FILES=$(echo "$CHANGED_FILES" | grep -v -E '.*\.Dockerfile|^releases/.*/files/|^\.github/|^docs/|\.md$|^renovate\.json$' || true)
139+
if [ -z "$RELEVANT_FILES" ]; then
140+
echo "Only ignored paths were changed. Skipping workflow."
141+
echo "skip=true" >> $GITHUB_OUTPUT
142+
else
143+
echo "skip=false" >> $GITHUB_OUTPUT
144+
fi
145+
else
146+
echo "skip=false" >> $GITHUB_OUTPUT
147+
fi
148+
- name: Set kernel settings
149+
if: steps.check_changes.outputs.skip != 'true'
150+
run: |
151+
sudo sysctl -w fs.inotify.max_user_instances=512
152+
sudo sysctl -w net.ipv4.ip_unprivileged_port_start=80
153+
# Mount BPF filesystem on the host (needed by kindnet on some kernels)
154+
sudo mount bpffs /sys/fs/bpf -t bpf -o nosuid,nodev,noexec,relatime || true
155+
- name: Install podman-compose
156+
if: steps.check_changes.outputs.skip != 'true'
157+
run: |
158+
sudo apt-get update -qq
159+
sudo apt-get install -y podman-compose
160+
# Ensure podman-compose is used instead of the docker-compose plugin shim
161+
sudo ln -sf /usr/bin/podman-compose /usr/local/bin/podman-compose
162+
- name: Install dependencies
163+
if: steps.check_changes.outputs.skip != 'true'
164+
run: |
165+
mkdir -p $HOME/.local/bin && echo "$HOME/.local/bin" >> "$GITHUB_PATH"
166+
curl -L https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 -o $HOME/.local/bin/kind
167+
chmod +x $HOME/.local/bin/kind
168+
curl -L https://github.com/helmfile/helmfile/releases/download/v${HELMFILE_VERSION}/helmfile_${HELMFILE_VERSION}_linux_amd64.tar.gz | tar -zx
169+
mv helmfile $HOME/.local/bin/helmfile
170+
curl -L "https://packages.cloudfoundry.org/stable?release=linux64-binary&version=${CF_CLI_VERSION}&source=github" | tar -zx
171+
mv cf8 $HOME/.local/bin/cf
172+
env:
173+
# renovate: dataSource=github-releases depName=cloudfoundry/cli
174+
CF_CLI_VERSION: "8.18.3"
175+
# renovate: dataSource=github-releases depName=kubernetes-sigs/kind
176+
KIND_VERSION: "0.31.0"
177+
# renovate: dataSource=github-releases depName=helmfile/helmfile
178+
HELMFILE_VERSION: "1.5.1"
179+
- name: Run make up (default)
180+
if: steps.check_changes.outputs.skip != 'true' && github.event_name == 'pull_request'
181+
run: make up
182+
env:
183+
CONTAINER_RUNTIME: podman
184+
- name: Run make up
185+
if: steps.check_changes.outputs.skip != 'true' && github.event_name != 'pull_request'
186+
run: make up
187+
env:
188+
CONTAINER_RUNTIME: podman
189+
INSTALL_OPTIONAL_COMPONENTS: ${{ inputs.minimal }}
190+
- name: Login
191+
if: steps.check_changes.outputs.skip != 'true'
192+
run: make login
193+
- name: Init
194+
if: steps.check_changes.outputs.skip != 'true'
195+
run: make bootstrap-complete
196+
- name: setup CF tests
197+
if: steps.check_changes.outputs.skip != 'true'
198+
uses: ./.github/actions/setup-cf-tests
199+
with:
200+
test-repo: cf-smoke-tests
201+
test-branch: main
202+
config-template: ./.github/smoke-config.tpl
203+
config-output: ./.github/smoke-config.json
204+
- name: run smoke test
205+
if: steps.check_changes.outputs.skip != 'true'
206+
env:
207+
CONFIG: "${{ github.workspace }}/.github/smoke-config.json"
208+
GINKGO_NO_COLOR: "true"
209+
run: |
210+
./cf-smoke-tests/bin/test --no-color --github-output --timeout=30m --procs=4 --json-report report.json
211+
- name: debug events
212+
if: failure()
213+
run: kubectl get events -A --sort-by='.lastTimestamp'
214+
- name: debug cilium
215+
if: failure()
216+
run: |
217+
echo "===== CILIUM PODS ====="
218+
kubectl get pod -n kube-system -l k8s-app=cilium -o wide
219+
echo "===== CILIUM AGENT LOGS (all pods) ====="
220+
for pod in $(kubectl get pod -n kube-system -l k8s-app=cilium -o jsonpath='{.items[*].metadata.name}'); do
221+
echo "--- $pod ---"
222+
kubectl logs -n kube-system "$pod" --all-containers=true --previous 2>/dev/null || kubectl logs -n kube-system "$pod" --all-containers=true 2>/dev/null || true
223+
done
224+
echo "===== CILIUM OPERATOR LOGS ====="
225+
kubectl logs -n kube-system -l name=cilium-operator --all-containers=true 2>/dev/null || true
226+
echo "===== CILIUM STATUS ====="
227+
kubectl exec -n kube-system $(kubectl get pod -n kube-system -l k8s-app=cilium -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) -- cilium status 2>/dev/null || true
228+
- name: debug pods
229+
if: failure()
230+
run: |
231+
echo "===== CLOUD_CONTROLLER ====="
232+
kubectl get pod -l app.kubernetes.io/name=cloud-controller -o wide
233+
kubectl get pod -l app.kubernetes.io/name=cloud-controller -o jsonpath='{.status.containerStatuses[*].state}' | jq
234+
kubectl logs -l app.kubernetes.io/name=cloud-controller --all-containers=true
235+
echo "===== CC_WORKER ====="
236+
kubectl get pod -l app.kubernetes.io/name=cc-worker -o wide
237+
kubectl logs -l app.kubernetes.io/name=cc-worker
238+
echo "===== CC_UPLOADER ====="
239+
kubectl get pod -l app=cc-uploader -o wide
240+
kubectl logs -l app=cc-uploader
241+
echo "===== CC_DEPLOYMENT_UPDATER ====="
242+
kubectl get pod -l app.kubernetes.io/name=cc-deployment-updater -o wide
243+
kubectl logs -l app.kubernetes.io/name=cc-deployment-updater
244+
echo "===== CC_WORKER_CLOCK ====="
245+
kubectl get pod -l app.kubernetes.io/name=cc-worker-clock -o wide
246+
kubectl logs -l app.kubernetes.io/name=cc-worker-clock
247+
echo "===== CF_TCP_ROUTER ====="
248+
kubectl get pod -n cf-system -l app=cf-tcp-router -o wide
249+
kubectl get pod -n cf-system -l app=cf-tcp-router -o jsonpath='{.items[*].status.containerStatuses[*].state}' | jq 2>/dev/null || true
250+
kubectl logs -n cf-system -l app=cf-tcp-router --all-containers=true 2>/dev/null || true
251+
echo "===== ROUTING_API ====="
252+
kubectl get pod -n cf-system -l app=routing-api -o wide
253+
kubectl logs -n cf-system -l app=routing-api --all-containers=true 2>/dev/null || true
254+
- uses: actions/upload-artifact@v7
255+
if: always()
256+
with:
257+
name: report-podman.json
258+
path: ./cf-smoke-tests/report.json
259+

Makefile

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,31 @@ TARGET_ARCH ?= $(if $(filter true,$(LOCAL)),$(shell go env GOARCH),amd64)
33
# renovate: dataSource=github-releases depName=helmfile/helmfile
44
HELMFILE_VERSION ?= "1.5.1"
55

6+
# Build all images for the local architecture (arm64 on Apple Silicon, amd64 elsewhere).
7+
# This ensures Go binaries like storage-cli are native – not run under Rosetta,
8+
# which causes 'taggedPointerPack' panics with high memory addresses.
9+
build:
10+
@ . ./scripts/detect-runtime.sh; \
11+
if [ "$$CONTAINER_RUNTIME" = "podman" ]; then \
12+
echo "Building with Podman is not yet supported via docker-bake.hcl."; \
13+
echo "Use 'podman build' manually with the Dockerfiles in releases/."; \
14+
exit 1; \
15+
fi; \
16+
docker buildx bake --file docker-bake.hcl --set "*.platform=linux/$(TARGET_ARCH)" $(BAKE_TARGETS)
17+
618
init: temp/certs/ca.key temp/certs/ca.crt temp/certs/ssh_key temp/certs/ssh_key.pub temp/secrets.sh temp/secrets.env
719

820
temp/certs/ca.key temp/certs/ca.crt temp/certs/ssh_key temp/certs/ssh_key.pub temp/secrets.sh temp/secrets.env:
921
@ ./scripts/init.sh
1022

1123
install:
12-
kind get kubeconfig --name cfk8s > temp/kubeconfig
13-
docker run --rm --net=host --env-file temp/secrets.env \
24+
@ . ./scripts/detect-runtime.sh; \
25+
if [ "$$IS_PODMAN" = "true" ]; then export SKIP_CILIUM="true"; fi; \
26+
kind get kubeconfig --name cfk8s > temp/kubeconfig; \
27+
$$CONTAINER_RUNTIME run --rm --net=host --env-file temp/secrets.env \
1428
--env INSTALL_OPTIONAL_COMPONENTS \
29+
--env CILIUM_EXTRA_VALUES \
30+
--env SKIP_CILIUM \
1531
-v "$$PWD/temp/certs:/certs" -v "$$PWD/temp/kubeconfig:/helm/.kube/config:ro" -v "$$PWD:/wd" --workdir /wd ghcr.io/helmfile/helmfile:v$(HELMFILE_VERSION) helmfile sync
1632

1733
login:
@@ -33,7 +49,7 @@ create-org:
3349
bootstrap: create-org
3450
@ ./scripts/upload_buildpacks.sh
3551

36-
bootstrap-complete: create-org
52+
bootstrap-complete: create-org
3753
@ ALL_BUILDPACKS=true ./scripts/upload_buildpacks.sh
3854

3955
up: create-kind init install

README.md

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@ This repository provides a simple and fast way to run Cloud Foundry locally. It
66

77
The following tools need to be installed:
88

9-
- [`docker`](https://docs.docker.com/engine/install/)
9+
- [`docker`](https://docs.docker.com/engine/install/) or [`podman`](https://podman.io/docs/installation) (v4.0 or higher)
10+
- [`docker-compose`](https://docs.docker.com/compose/install) or [`podman-compose`](https://github.com/containers/podman-compose) (v1.0 or higher)
1011
- [`kind`](https://kind.sigs.k8s.io/docs/user/quick-start/#installing-from-release-binaries) (v0.31.0 or higher)
1112
- [`kubectl`](https://kubernetes.io/docs/tasks/tools/#kubectl) (v1.35.1 or higher)
1213
- `make`:
1314
- It should be already installed on MacOS and Linux.
1415
- For Windows installation see: <https://gnuwin32.sourceforge.net/packages/make.htm>
1516

17+
The container runtime is auto-detected. Set `CONTAINER_RUNTIME=docker` or `CONTAINER_RUNTIME=podman` to override.
18+
1619
## Run the Installation
1720

1821
```bash
@@ -48,6 +51,40 @@ You can configure the installation by setting the environment variable `INSTALL_
4851

4952
`bosh-dns`, `cf-tcp-router`, `credhub`, `loggregator`, `nfsbroker`, `policy-agent`, `policy-server`, `routing-api`, `service-discovery-controller`
5053

54+
## Podman Support
55+
56+
Podman is supported as a drop-in replacement for Docker. The runtime is detected automatically; no aliasing is required.
57+
58+
### Linux (rootless Podman)
59+
60+
Rootless Podman is fully supported on Linux. The following kernel settings must be applied before running `make up` — either manually or via your system's sysctl configuration:
61+
62+
```bash
63+
sudo sysctl -w fs.inotify.max_user_instances=512
64+
sudo sysctl -w net.ipv4.ip_unprivileged_port_start=80
65+
```
66+
67+
The first setting prevents inotify exhaustion under heavy workloads. The second allows the kind node containers and pods to bind privileged ports (80, 443, 2222) without root.
68+
69+
### macOS / Windows (Podman Desktop)
70+
71+
A Podman machine is created and configured automatically by `make up`. The machine is created in rootful mode with 4 CPUs, 8 GB RAM, and 60 GB disk. No manual configuration is needed.
72+
73+
### Limitations
74+
75+
- **CNI:** Cilium is skipped under rootless Podman (Linux CI / rootless desktop) because Cilium 1.18.x requires `CAP_NET_ADMIN` in the host user namespace, which rootless containers cannot provide. [kindnet](https://github.com/aojea/kindnet) is used instead, providing full pod-to-pod connectivity without eBPF privileges. Cilium network policies are therefore not enforced in this mode.
76+
- **Image builds:** `make build` (which uses `docker buildx bake`) is not supported with Podman. Use `podman build` directly with the Dockerfiles in `releases/` for local image development.
77+
78+
## ARM / Apple Silicon Limitations
79+
80+
The CF stack (`cflinuxfs4`) and all buildpacks are **x86-64 (amd64) only**. CF applications run inside `cflinuxfs4` rootfs containers, which are amd64 images and require x86 emulation on ARM hosts.
81+
82+
- **Docker Desktop on Apple Silicon:** Enable Rosetta emulation (Settings → General → Use Rosetta for x86_64/amd64 emulation). This is the recommended and well-tested path.
83+
- **Podman on Apple Silicon:** The Podman machine is created with `--rootful` and runs under QEMU/Rosetta. Functional, but noticeably slower than Docker Desktop with Rosetta.
84+
- **Linux ARM64:** Not supported. The `cflinuxfs4` stack image and pre-compiled buildpack zip files are amd64-only. CF app staging and execution will fail on a native ARM64 host without kernel-level x86 emulation (`binfmt_misc` with QEMU).
85+
86+
The CF platform components themselves (gorouter, diego, CAPI, etc.) are built for the local architecture (`make build` targets the native arch), so control-plane operations are native-speed on ARM. Only the **application workload layer** (buildpacks, cflinuxfs4 rootfs) is restricted to amd64.
87+
5188
## Unsupported Features
5289

5390
- Routing isolation segments are not fully feature complete since this relies on more than one gateway which is not possible to realize in a local kind setup (see [FAQ](./docs/faq.md))
@@ -61,4 +98,4 @@ You can configure the installation by setting the environment variable `INSTALL_
6198

6299
Please check our [contributing guidelines](/CONTRIBUTING.md).
63100

64-
This project follows [Cloud Foundry Code of Conduct](https://www.cloudfoundry.org/code-of-conduct/).
101+
This project follows [Cloud Foundry Code of Conduct](https://www.cloudfoundry.org/code-of-conduct/)

assets/base-chart/templates/network-policy.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{{- if .Values.ciliumNetworkPolicies.enabled }}
12
---
23
apiVersion: cilium.io/v2
34
kind: CiliumClusterwideNetworkPolicy
@@ -85,3 +86,4 @@ spec:
8586
- ports:
8687
- port: "8443"
8788
protocol: TCP
89+
{{- end }}

assets/base-chart/values.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,7 @@ caCert: ""
22
caKey: ""
33
tlsCert: ""
44
tlsKey: ""
5+
# Set to false when Cilium is not installed (e.g. rootless Podman CI).
6+
# CiliumClusterwideNetworkPolicy CRDs won't exist without Cilium.
7+
ciliumNetworkPolicies:
8+
enabled: true

assets/values/cilium-podman.yaml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
ipam:
2+
mode: kubernetes
3+
envoy:
4+
enabled: false
5+
# kube-proxy replacement via eBPF requires privileges not available in
6+
# rootless Podman containers (used in CI). Fall back to standard kube-proxy mode.
7+
kubeProxyReplacement: false
8+
k8sServiceHost: cfk8s-control-plane
9+
k8sServicePort: 6443
10+
operator:
11+
replicas: 1
12+
# Use VXLAN tunnel instead of native routing (required without elevated privileges)
13+
routingMode: tunnel
14+
tunnelProtocol: vxlan
15+
# Disable all eBPF features that require CAP_BPF / kernel privileges
16+
# not available inside rootless Podman-spawned KinD nodes.
17+
bpf:
18+
masquerade: false
19+
# Disable automatic BPF filesystem mounting – the host already has bpffs mounted
20+
# at /sys/fs/bpf and the init-container mount syscall fails inside Podman containers.
21+
autoMount:
22+
enabled: false
23+
preallocateMaps: false
24+
hostServices:
25+
enabled: false
26+
nodePort:
27+
enabled: false
28+
socketLB:
29+
enabled: false
30+
externalIPs:
31+
enabled: false
32+
# Disable Hubble to reduce resource usage and avoid additional eBPF probes
33+
hubble:
34+
enabled: false
35+
# Tell Cilium where the cgroup root is (since we can't auto-mount)
36+
cgroup:
37+
autoMount:
38+
enabled: false
39+
hostRoot: /sys/fs/cgroup
40+
# Disable bandwidth manager (requires eBPF)
41+
bandwidthManager:
42+
enabled: false
43+
localRedirectPolicy: false
44+
# Use iptables for masquerading instead of eBPF
45+
enableIPv4Masquerade: true
46+
# Disable DSR which requires eBPF
47+
loadBalancer:
48+
mode: snat
49+
# Disable sysctl override file creation – Cilium tries to write
50+
# /etc/sysctl.d/99-zzz-override_cilium.conf which fails in rootless
51+
# Podman KinD nodes that lack CAP_SYS_ADMIN / write access to /etc/sysctl.d/.
52+
sysctlfix:
53+
enabled: false
54+
# Disable host firewall – requires loading BPF programs into cgroup hooks
55+
# (CGroupSock, CGroupSockAddr, etc.) which fail with EPERM in rootless Podman.
56+
hostFirewall:
57+
enabled: false
58+
# Disable session affinity – it triggers CGroupSock BPF helper probing
59+
# (FnSetRetval) which fails with EPERM in rootless Podman KinD nodes.
60+
sessionAffinity: false
61+
# Disable EnvoyConfig CRD processing to avoid additional BPF probes
62+
enableEnvoyConfig: false

0 commit comments

Comments
 (0)