-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathContainerfile
More file actions
290 lines (251 loc) · 12.6 KB
/
Copy pathContainerfile
File metadata and controls
290 lines (251 loc) · 12.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
# Containerfile for devaipod
#
# Builds devaipod as a container image for orchestrating AI agent workspaces.
# The container requires access to the host's podman socket to spawn sibling
# containers (workspace pods).
#
# Build:
# podman build --tag ghcr.io/cgwalters/devaipod -f Containerfile .
#
# Run (web UI mode - default):
# podman volume create devaipod-state # optional; just container-run creates it
# SOCKET=$XDG_RUNTIME_DIR/podman/podman.sock
# podman run -d --name devaipod -p 8080:8080 --privileged \
# -v devaipod-state:/var/lib/devaipod \
# -v $SOCKET:/run/docker.sock \
# -e DEVAIPOD_HOST_SOCKET=$SOCKET \
# -v ~/.config/devaipod.toml:/root/.config/devaipod.toml:ro \
# ghcr.io/cgwalters/devaipod
#
# The devaipod-state volume stores the web auth token by default (at
# /var/lib/devaipod/web-token) so it persists across container restarts.
#
# # Get the web UI URL with auth token from logs (first run only; later use same URL):
# podman logs devaipod | grep "Web UI"
#
# Run with stable auth token (via podman secret) instead of state volume:
# openssl rand -base64 32 | podman secret create devaipod-web-token -
# SOCKET=$XDG_RUNTIME_DIR/podman/podman.sock
# podman run -d --name devaipod -p 8080:8080 --privileged \
# --secret devaipod-web-token \
# -v devaipod-state:/var/lib/devaipod \
# -v $SOCKET:/run/docker.sock \
# -e DEVAIPOD_HOST_SOCKET=$SOCKET \
# -v ~/.config/devaipod.toml:/root/.config/devaipod.toml:ro \
# ghcr.io/cgwalters/devaipod
#
# Interact via CLI:
# podman exec devaipod devaipod run https://github.com/org/repo -c 'fix bug'
# podman exec -ti devaipod devaipod attach -l
# podman exec -ti devaipod devaipod tui
#
# Note: --privileged is required for socket access and for spawning privileged
# workspace containers (needed for nested podman in devcontainers).
#
# Uses BuildKit-style cache mounts for fast incremental Rust builds.
# -- source snapshot (keeps layer graph clean) --
FROM scratch AS src
COPY . /src
# -- mdbook documentation build stage --
# Builds the project documentation using mdbook. The output (static HTML) is
# copied into the final image and served by the control plane web server.
FROM docker.io/library/rust:slim AS mdbook
RUN cargo install mdbook@0.4.52 mdbook-mermaid@0.16.0 --locked
COPY --from=src /src/docs /src/docs
WORKDIR /src/docs
RUN mdbook-mermaid install . && mdbook build
# Output is in /src/docs/book
# -- opencode web UI build stage --
# Build the vendored opencode UI fork (opencode-ui/) which includes devaipod
# customizations: workspace terminal, git review tab, per-pod localStorage, etc.
#
# Layer ordering is optimised for cache reuse: dependency metadata first, then
# install, then the full source tree. This mirrors the Rust build stage pattern.
ARG OPENCODE_VERSION=v1.1.65
FROM docker.io/oven/bun:latest AS opencode-web
ARG OPENCODE_VERSION=v1.1.65
# Disable apt sandboxing for nested container environments (e.g. building
# inside a devcontainer with rootless podman). Without this, apt fails with
# "setgroups 65534 failed" when the outer container lacks CAP_SETGID for
# the _apt user. See bootc-dev/infra@491e950.
RUN echo 'APT::Sandbox::User "root";' > /etc/apt/apt.conf.d/99sandbox-disable
# Fonts are gitignored (~60MB binary); fetch from upstream for the build.
RUN apt-get update && apt-get install -y --no-install-recommends \
git ca-certificates && rm -rf /var/lib/apt/lists/*
RUN git clone --depth 1 --filter=blob:none --sparse \
--branch ${OPENCODE_VERSION} \
https://github.com/anomalyco/opencode.git /tmp/oc && \
cd /tmp/oc && git sparse-checkout set packages/ui/src/assets/fonts
WORKDIR /build
# 1. Copy only dependency-related files first so that `bun install` is cached
# unless package.json, bun.lock, or workspace package.json files change.
COPY --from=src /src/opencode-ui/package.json /src/opencode-ui/bun.lock /src/opencode-ui/bunfig.toml /build/
COPY --from=src /src/opencode-ui/packages/app/package.json /build/packages/app/package.json
COPY --from=src /src/opencode-ui/packages/ui/package.json /build/packages/ui/package.json
COPY --from=src /src/opencode-ui/packages/sdk/js/package.json /build/packages/sdk/js/package.json
COPY --from=src /src/opencode-ui/packages/util/package.json /build/packages/util/package.json
# Patches referenced in patchedDependencies must be present for install.
COPY --from=src /src/opencode-ui/patches /build/patches
RUN bun install --frozen-lockfile
# 2. Now copy the full source and fonts (changes here skip the install layer).
COPY --from=src /src/opencode-ui /build
RUN mkdir -p packages/ui/src/assets/fonts && \
cp /tmp/oc/packages/ui/src/assets/fonts/*.woff2 packages/ui/src/assets/fonts/
WORKDIR /build/packages/app
RUN bun run build
# Output is in /build/packages/app/dist
# -- opencode CLI binary --
# Download the opencode CLI for use in advisor pods (where this image is the agent image).
# Release artifacts are tarballs; select the right architecture at build time.
# TARGETARCH is set by buildx/podman (amd64 or arm64); opencode uses x64/arm64.
FROM quay.io/centos/centos:stream10 AS opencode-cli
ARG OPENCODE_VERSION=v1.1.65
ARG TARGETARCH
RUN ARCH="${TARGETARCH}"; \
if [ "$ARCH" = "amd64" ] || [ -z "$ARCH" ]; then ARCH="x64"; fi; \
curl -fsSL \
"https://github.com/anomalyco/opencode/releases/download/${OPENCODE_VERSION}/opencode-linux-${ARCH}.tar.gz" \
| tar xzf - -C /usr/local/bin/ opencode && \
chmod +x /usr/local/bin/opencode
# -- build stage --
FROM quay.io/centos/centos:stream10 AS build
RUN dnf install -y \
rust cargo \
openssl-devel \
gcc \
git \
&& dnf clean all
COPY --from=src /src /src
WORKDIR /src
# Fetch dependencies (network-intensive, cached separately)
RUN --mount=type=cache,target=/src/target \
--mount=type=cache,target=/root/.cargo/registry \
--mount=type=cache,target=/root/.cargo/git \
cargo fetch
# Build devaipod binary
RUN --network=none \
--mount=type=cache,target=/src/target \
--mount=type=cache,target=/root/.cargo/registry \
--mount=type=cache,target=/root/.cargo/git \
cargo build --release -p devaipod && \
cp /src/target/release/devaipod /usr/bin/devaipod
# -- unit tests (built from the build stage, run via `just test-container`) --
FROM build AS units
RUN --network=none \
--mount=type=cache,target=/src/target \
--mount=type=cache,target=/root/.cargo/registry \
--mount=type=cache,target=/root/.cargo/git \
mkdir -p /usr/lib/devaipod/units && \
cargo test --no-run -p devaipod --message-format=json 2>/dev/null \
| python3 -c 'import sys,json;[print(m["executable"])for line in sys.stdin for m in[json.loads(line)]if m.get("profile",{}).get("test")and m.get("executable")]' \
| while read bin; do install -m 0755 "$bin" "/usr/lib/devaipod/units/$(basename $bin)"; done && \
test -n "$(ls /usr/lib/devaipod/units/)" && \
printf '#!/bin/bash\nset -xeuo pipefail\nfor f in /usr/lib/devaipod/units/*; do echo "$f" && "$f"; done\n' \
> /usr/bin/devaipod-units && chmod a+x /usr/bin/devaipod-units
# -- integration tests (built from the build stage) --
# Builds integration test binaries into a separate directory. These tests
# require the devaipod binary and runtime dependencies (podman, git, etc.)
# to exercise the full workflow including container orchestration.
FROM build AS integration
# Build integration test binaries in release mode. The devaipod release binary
# is already at /usr/bin/devaipod from the build stage.
# First compile with normal output so errors are visible and fail the build,
# then re-run with --message-format=json to extract the binary paths.
RUN --network=none \
--mount=type=cache,target=/src/target \
--mount=type=cache,target=/root/.cargo/registry \
--mount=type=cache,target=/root/.cargo/git \
cargo test --release --no-run -p integration-tests 2>&1 && \
mkdir -p /usr/lib/devaipod/integration && \
cargo test --release --no-run -p integration-tests --message-format=json 2>/dev/null \
| python3 -c 'import sys,json;[print(m["executable"])for line in sys.stdin for m in[json.loads(line)]if m.get("profile",{}).get("test")and m.get("executable")]' \
| while read bin; do install -m 0755 "$bin" "/usr/lib/devaipod/integration/$(basename $bin)"; done && \
test -n "$(ls /usr/lib/devaipod/integration/)" && \
printf '#!/bin/bash\nset -xeuo pipefail\nfor f in /usr/lib/devaipod/integration/*; do echo "$f" && "$f"; done\n' \
> /usr/bin/devaipod-integration && chmod a+x /usr/bin/devaipod-integration
# -- integration test runner (minimal runtime image) --
# Contains integration test binaries plus runtime deps needed to run tests.
# Build: podman build --target integration-runner -t localhost/devaipod-integration .
# Run: podman run --rm --privileged -v /run/podman/podman.sock:/run/docker.sock localhost/devaipod-integration
FROM quay.io/centos/centos:stream10 AS integration-runner
RUN dnf install -y \
podman-remote \
git \
openssh-clients \
tmux \
&& dnf clean all \
&& ln -sf /usr/bin/podman-remote /usr/bin/podman
COPY --from=integration /usr/lib/devaipod/integration /usr/lib/devaipod/integration
COPY --from=integration /usr/bin/devaipod-integration /usr/bin/devaipod-integration
COPY --from=integration /usr/bin/devaipod /usr/bin/devaipod
ENV DEVAIPOD_CONTAINER=1
ENV CONTAINER_HOST=unix:///run/docker.sock
CMD ["/usr/bin/devaipod-integration"]
# -- web integration test runner (Playwright + Chromium) --
# Browser-driven tests for the devaipod control plane UI (pod switcher,
# git review tab, etc.). Based on the official Playwright image which
# bundles Chromium, Firefox, and WebKit.
# Build: podman build --target integration-web-runner -t localhost/devaipod-integration-web .
# Run: podman run --rm --privileged -v /run/podman/podman.sock:/run/docker.sock localhost/devaipod-integration-web
FROM mcr.microsoft.com/playwright:v1.57.0-noble AS integration-web-runner
# podman-remote for pod lifecycle (same pattern as integration-runner)
RUN apt-get update && apt-get install -y --no-install-recommends \
podman-remote git \
&& ln -sf /usr/bin/podman-remote /usr/bin/podman \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# The devaipod binary + SPA assets (needed for serving the web UI)
COPY --from=build /usr/bin/devaipod /usr/bin/devaipod
COPY --from=opencode-web /build/packages/app/dist /usr/share/devaipod/opencode
COPY --from=mdbook /src/docs/book /usr/share/devaipod/docs
# Playwright test harness
COPY --from=src /src/e2e-devaipod/ /opt/e2e-devaipod/
WORKDIR /opt/e2e-devaipod
RUN npm ci && npx playwright install --with-deps chromium
ENV DEVAIPOD_CONTAINER=1
ENV CONTAINER_HOST=unix:///run/docker.sock
CMD ["npx", "playwright", "test"]
# -- final minimal image --
FROM quay.io/centos/centos:stream10
# Install runtime dependencies:
# - podman-remote: for CLI fallback operations (exec, etc.)
# - git: for repository operations
# - openssh-clients: for SSH integration
# - tmux: for attach command's split-pane UI
RUN dnf install -y \
podman-remote \
git \
openssh-clients \
tmux \
&& dnf clean all \
&& ln -sf /usr/bin/podman-remote /usr/bin/podman
# Create config and state directories
RUN mkdir -p /root/.config /var/lib/devaipod
COPY --from=build /usr/bin/devaipod /usr/bin/devaipod
# Install the opencode CLI agent binary; needed when this image is used as
# the agent container for advisor pods.
COPY --from=opencode-cli /usr/local/bin/opencode /usr/local/bin/opencode
# Mark that we're running inside the official devaipod container
# This is checked by `devaipod` to require running in container mode by default
ENV DEVAIPOD_CONTAINER=1
# Tell podman-remote (aliased as podman) where the socket is mounted.
# Without this, bare `podman` commands default to /run/podman/podman.sock.
ENV CONTAINER_HOST=unix:///run/docker.sock
# Copy vendored opencode web UI fork (built from opencode-ui/ in the repo)
# This is served at /opencode/ and proxies API calls to the agent's opencode server
COPY --from=opencode-web /build/packages/app/dist /usr/share/devaipod/opencode
# Copy mdbook documentation (served at /docs/ by the control plane)
COPY --from=mdbook /src/docs/book /usr/share/devaipod/docs
WORKDIR /usr/share/devaipod
# Default: run web UI server
# The web server prints a URL with auth token to stdout on startup.
# Alternative entrypoints:
# - `devaipod tui` for interactive TUI dashboard
# - `sleep infinity` then use `podman exec` for commands
# - Custom command for one-shot operations
#
# Example:
# podman run -d -p 8080:8080 ... ghcr.io/cgwalters/devaipod
# # Copy the URL with token from logs:
# podman logs <container> | grep "Web UI"
EXPOSE 8080
CMD ["devaipod", "web", "--port", "8080"]