Skip to content

Commit 7cd6ad9

Browse files
committed
Add devcontainer and container GC pipeline
Motivated by having a good Codespaces default UX. This repo will also contain our centralized container image GC. Assisted-by: Cursor (Claude Sonnet 4.5) Signed-off-by: Colin Walters <walters@verbum.org>
1 parent f775bf5 commit 7cd6ad9

18 files changed

Lines changed: 881 additions & 0 deletions

.cargo/config.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Alias for cargo xtask
2+
[alias]
3+
xtask = "run --package xtask --"
4+

.devcontainer/devcontainer.json

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"name": "bootc-dev",
3+
"image": "ghcr.io/bootc-dev/infra/devenv:main",
4+
"build": {
5+
"context": "..",
6+
"dockerfile": "../devenv/Containerfile"
7+
},
8+
"customizations": {
9+
"vscode": {
10+
"extensions": [
11+
"rust-lang.rust-analyzer",
12+
"tamasfe.even-better-toml"
13+
]
14+
}
15+
},
16+
"features": {},
17+
"remoteUser": "root",
18+
"mounts": [
19+
"source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind"
20+
],
21+
"runArgs": [
22+
"--privileged"
23+
],
24+
"postCreateCommand": "echo 'DevContainer ready!'",
25+
"remoteEnv": {
26+
"PATH": "${containerEnv:PATH}:/usr/local/cargo/bin"
27+
}
28+
}
29+
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
name: Build DevContainer
2+
3+
on:
4+
push:
5+
branches: [main]
6+
tags: ['v*']
7+
pull_request:
8+
paths:
9+
- 'devenv/**'
10+
- '.github/workflows/build-devcontainer.yml'
11+
12+
env:
13+
REGISTRY: ghcr.io
14+
IMAGE_NAME: ${{ github.repository }}/devenv
15+
16+
jobs:
17+
build:
18+
runs-on: ${{ matrix.runner }}
19+
strategy:
20+
fail-fast: false
21+
matrix:
22+
include:
23+
- runner: ubuntu-latest
24+
platform: linux/amd64
25+
arch: amd64
26+
- runner: ubuntu-24.04-arm
27+
platform: linux/arm64
28+
arch: arm64
29+
30+
steps:
31+
- name: Checkout
32+
uses: actions/checkout@v4
33+
34+
- name: Set up Docker Buildx
35+
uses: docker/setup-buildx-action@v3
36+
37+
- name: Log in to Container Registry
38+
uses: docker/login-action@v3
39+
with:
40+
registry: ${{ env.REGISTRY }}
41+
username: ${{ github.actor }}
42+
password: ${{ secrets.GITHUB_TOKEN }}
43+
44+
- name: Extract metadata
45+
id: meta
46+
uses: docker/metadata-action@v5
47+
with:
48+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
49+
50+
- name: Build and push by digest
51+
id: build
52+
uses: docker/build-push-action@v6
53+
with:
54+
context: devenv
55+
file: devenv/Containerfile
56+
platforms: ${{ matrix.platform }}
57+
labels: ${{ steps.meta.outputs.labels }}
58+
outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }}
59+
60+
- name: Export digest
61+
run: |
62+
mkdir -p /tmp/digests
63+
digest="${{ steps.build.outputs.digest }}"
64+
touch "/tmp/digests/${digest#sha256:}"
65+
66+
- name: Upload digest
67+
uses: actions/upload-artifact@v4
68+
with:
69+
name: digests-${{ matrix.arch }}
70+
path: /tmp/digests/*
71+
if-no-files-found: error
72+
retention-days: 1
73+
74+
merge:
75+
runs-on: ubuntu-latest
76+
needs: build
77+
if: github.event_name != 'pull_request'
78+
steps:
79+
- name: Download digests
80+
uses: actions/download-artifact@v4
81+
with:
82+
path: /tmp/digests
83+
pattern: digests-*
84+
merge-multiple: true
85+
86+
- name: Set up Docker Buildx
87+
uses: docker/setup-buildx-action@v3
88+
89+
- name: Docker meta
90+
id: meta
91+
uses: docker/metadata-action@v5
92+
with:
93+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
94+
tags: |
95+
type=raw,value=latest,enable={{is_default_branch}}
96+
type=sha,prefix={{branch}}-,format=short
97+
type=sha,prefix={{branch}}-,format=long
98+
type=ref,event=pr
99+
type=ref,event=tag
100+
101+
- name: Log in to Container Registry
102+
uses: docker/login-action@v3
103+
with:
104+
registry: ${{ env.REGISTRY }}
105+
username: ${{ github.actor }}
106+
password: ${{ secrets.GITHUB_TOKEN }}
107+
108+
- name: Create manifest list and push
109+
working-directory: /tmp/digests
110+
run: |
111+
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
112+
$(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
113+
114+
- name: Inspect image
115+
run: |
116+
docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
117+

.github/workflows/container-gc.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: Container Image Garbage Collection
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
retention-days:
7+
description: "Delete container images older than this many days"
8+
required: false
9+
default: "14"
10+
type: string
11+
dry-run:
12+
description: "Dry run mode - don't actually delete anything"
13+
required: false
14+
default: false
15+
type: boolean
16+
schedule:
17+
# Run weekly on Sundays at 2 AM UTC
18+
- cron: '0 2 * * 0'
19+
20+
env:
21+
ORG: bootc-dev
22+
RETENTION_DAYS: ${{ github.event.inputs.retention-days || '14' }}
23+
DRY_RUN: ${{ github.event.inputs.dry-run || 'false' }}
24+
25+
jobs:
26+
cleanup:
27+
runs-on: ubuntu-latest
28+
permissions:
29+
contents: read
30+
packages: write
31+
32+
steps:
33+
- name: Checkout
34+
uses: actions/checkout@v4
35+
36+
- name: Install Rust toolchain
37+
uses: dtolnay/rust-toolchain@stable
38+
39+
- name: Cache Cargo dependencies
40+
uses: Swatinem/rust-cache@v2
41+
with:
42+
workspaces: "."
43+
44+
- name: Run container garbage collection
45+
env:
46+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
47+
run: |
48+
cargo xtask container-gc --retention-days ${{ env.RETENTION_DAYS }} --dry-run ${{ env.DRY_RUN }} --org ${{ env.ORG }}
49+

.github/workflows/rust.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Rust CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
workflow_dispatch:
9+
10+
env:
11+
CARGO_TERM_COLOR: always
12+
13+
concurrency:
14+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
15+
cancel-in-progress: true
16+
17+
jobs:
18+
build:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- uses: actions/checkout@v4
22+
23+
- name: Install Rust toolchain
24+
uses: dtolnay/rust-toolchain@stable
25+
with:
26+
components: rustfmt, clippy
27+
28+
- name: Cache Cargo dependencies
29+
uses: Swatinem/rust-cache@v2
30+
with:
31+
workspaces: "."
32+
33+
- name: Check formatting
34+
run: cargo fmt --all -- --check
35+
36+
- name: Run clippy
37+
run: cargo clippy --all-targets -- -D warnings
38+
39+
- name: Build
40+
run: cargo build --release
41+
42+
- name: Run tests
43+
run: cargo test --all
44+

.gitignore

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Rust build artifacts
2+
target/
3+
Cargo.lock
4+
5+
# macOS
6+
.DS_Store
7+
8+
# Editor files
9+
*.swp
10+
*.swo
11+
*~
12+
.vscode/
13+
.idea/
14+
15+
# Environment files
16+
.env
17+

Cargo.toml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
[workspace]
2+
members = ["crates/*"]
3+
resolver = "2"
4+
5+
[profile.dev]
6+
opt-level = 1
7+
8+
[profile.release]
9+
lto = "thin"
10+
debug = true
11+
12+
[workspace.dependencies]
13+
anyhow = "1.0"
14+
chrono = { version = "0.4", default-features = false, features = ["std", "clock"] }
15+
clap = { version = "4.5", features = ["derive", "env"] }
16+
serde = { version = "1.0", features = ["derive"] }
17+
serde_json = "1.0"
18+
xshell = "0.2"
19+
20+
[workspace.lints.rust]
21+
unsafe_code = "deny"
22+
unused_must_use = "forbid"
23+
24+
[workspace.lints.clippy]
25+
dbg_macro = "deny"
26+
todo = "deny"
27+

Justfile

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Build devenv image with local tag
2+
devenv-build:
3+
cd devenv && podman build -t localhost/bootc-devenv .
4+
5+
# Build devenv image with proper tagging (latest + git SHA)
6+
devenv-build-tagged:
7+
#!/usr/bin/env bash
8+
set -euo pipefail
9+
GIT_SHA=$(git rev-parse --short HEAD)
10+
GIT_SHA_LONG=$(git rev-parse HEAD)
11+
BRANCH=$(git rev-parse --abbrev-ref HEAD)
12+
IMAGE_BASE="ghcr.io/bootc-dev/infra/devenv"
13+
cd devenv
14+
# Build with multiple tags
15+
podman build \
16+
-t "${IMAGE_BASE}:latest" \
17+
-t "${IMAGE_BASE}:${BRANCH}-${GIT_SHA}" \
18+
-t "${IMAGE_BASE}:${BRANCH}-${GIT_SHA_LONG}" \
19+
.
20+
echo "Built and tagged:"
21+
echo " - ${IMAGE_BASE}:latest"
22+
echo " - ${IMAGE_BASE}:${BRANCH}-${GIT_SHA}"
23+
echo " - ${IMAGE_BASE}:${BRANCH}-${GIT_SHA_LONG}"
24+
25+
# Push devenv image with all tags
26+
devenv-push: devenv-build-tagged
27+
#!/usr/bin/env bash
28+
set -euo pipefail
29+
GIT_SHA=$(git rev-parse --short HEAD)
30+
GIT_SHA_LONG=$(git rev-parse HEAD)
31+
BRANCH=$(git rev-parse --abbrev-ref HEAD)
32+
IMAGE_BASE="ghcr.io/bootc-dev/infra/devenv"
33+
podman push "${IMAGE_BASE}:latest"
34+
podman push "${IMAGE_BASE}:${BRANCH}-${GIT_SHA}"
35+
podman push "${IMAGE_BASE}:${BRANCH}-${GIT_SHA_LONG}"
36+
echo "Pushed all tags to registry"
37+
38+
# Run container garbage collection (dry run by default)
39+
container-gc-dry-run:
40+
cargo xtask container-gc --dry-run true --retention-days 14
41+
42+
# Run container garbage collection (actual deletion)
43+
container-gc:
44+
cargo xtask container-gc --retention-days 14
45+
46+
# Run container garbage collection with custom retention period
47+
container-gc-custom retention_days:
48+
cargo xtask container-gc --retention-days {{retention_days}}

0 commit comments

Comments
 (0)