File size: 15,238 Bytes
a87a9e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2f84d72
 
 
a87a9e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2f84d72
 
 
 
 
 
 
 
7ae219d
 
 
 
 
a87a9e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2f84d72
 
 
a87a9e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
---
license: odc-by
pretty_name: The Stack v3 DevOps Corpus
size_categories:
  - 10M<n<100M
task_categories:
  - text-generation
language:
  - code
tags:
  - infrastructure-as-code
  - devops
  - kubernetes
  - helm
  - terraform
  - ansible
  - docker
  - github-actions
  - sre
configs:
  - config_name: helm_chart
    data_files:
      - split: train
        path: data/helm_chart/train-*.parquet
  - config_name: terraform_module
    data_files:
      - split: train
        path: data/terraform_module/train-*.parquet
  - config_name: manifest_set
    data_files:
      - split: train
        path: data/manifest_set/train-*.parquet
  - config_name: ansible_role
    data_files:
      - split: train
        path: data/ansible_role/train-*.parquet
  - config_name: dockerfile
    data_files:
      - split: train
        path: data/dockerfile/train-*.parquet
  - config_name: workflow
    data_files:
      - split: train
        path: data/workflow/train-*.parquet
  - config_name: compose
    data_files:
      - split: train
        path: data/compose/train-*.parquet
---

# The Stack v3 DevOps Corpus

13,234,862 complete infrastructure units extracted from
[The Stack v3](https://huggingface.co/datasets/HuggingFaceCode/stack-v3-train),
grouped into seven classes and gated on content rather than popularity.

A unit is not a file, it is the thing an engineer would actually run: a Helm chart
arrives with its `Chart.yaml`, `values.yaml` and every template; a Terraform module
with all of its `.tf` files; an Ansible role with its tasks, defaults and handlers.
That is only possible because The Stack v3 groups rows by repository, which v2 did
not.

## Why this exists

Language detection cannot find infrastructure code. Helm templates, Kubernetes
manifests, Ansible playbooks, CI pipelines and Prometheus rules are all just
`YAML` to `go-enry`, and **59.6% of the YAML in the corpus is not infrastructure
at all** (Spring config, i18n plurals, Drupal exports, dbt models, Conda
environments). Path heuristics do not fix it either: a directory-name rule finds
only **33% of real Kubernetes manifests** and is **57% precise**.

So classification here is content-first. Every YAML unit was parsed and inspected,
and the resulting labels were scored against an independent YAML parser rather
than against more regexes:

| Class | Precision | Recall |
|---|---|---|
| kubernetes | 97.8% | 97.2% |
| github_actions | 98.9% | 100.0% |
| compose | 97.2% | 99.3% |
| helm | 93.9% (structural) | not measurable, templates are not valid YAML |
| ansible | 86.4% | 90.3% |
| terraform | 98.9% (extension-anchored) | |
| dockerfile | 99.7% (extension-anchored) | |

Roughly half of all Helm and Ansible labels come from repository context alone:
a `values.yaml` or a `defaults/main.yml` is a bare tree of variables, and no
amount of content inspection can tell you what it belongs to.

## Configs

| Config | Units | Parquet | What a unit is |
|---|---|---|---|
| `helm_chart` | 65,422 | 0.15 GB | Complete charts: `Chart.yaml` plus templates, and `values.yaml` where present |
| `terraform_module` | 779,730 | 1.06 GB | Directories with two or more `.tf` files declaring real blocks |
| `manifest_set` | 743,191 | 0.42 GB | Directories of two or more Kubernetes manifests that parse |
| `ansible_role` | 444,411 | 0.34 GB | Roles with a verifiable task list, plus defaults, handlers and templates |
| `dockerfile` | 4,609,451 | 1.14 GB | Single files containing real Dockerfile instructions |
| `workflow` | 3,380,313 | 1.41 GB | GitHub Actions workflows with triggers and jobs |
| `compose` | 3,212,344 | 0.87 GB | Docker Compose files with a services mapping |
| **total** | **13,234,862** | **5.40 GB** | |

### What is inside each one

- **`helm_chart`** the scarcest and richest class. `Chart.yaml`, `values.yaml`
  where present, every template and helper. Median 4 templates, up to 56.
- **`terraform_module`** a directory of two or more `.tf` files that declare real
  resources, modules, variables or outputs. Median 3 files. 70.7% declare
  variables, 47.8% outputs.
- **`manifest_set`** a directory of two or more Kubernetes manifests that parse.
  Median 3. Most common kinds: Deployment, Service, Kustomization, ConfigMap,
  Ingress, PersistentVolumeClaim, Secret.
- **`ansible_role`** `tasks/`, and whichever of `defaults/`, `handlers/`, `vars/`,
  `meta/`, `templates/`, `files/` the role ships. 24.6% carry defaults.
- **`dockerfile`** one file with real instructions. Median 8 instructions,
  20.3% multi-stage.
- **`workflow`** one GitHub Actions workflow with triggers and jobs. Median 1 job
  and 5 steps.
- **`compose`** one Compose file with a services mapping. Median 2 services.

### Using it

```python
from datasets import load_dataset

charts = load_dataset("Helmcode/stack-v3-devops", "helm_chart", split="train")
```

Charts that render standalone, which is what an executable benchmark needs:

```python
renderable = charts.filter(lambda row: row["flags"]["self_contained"])
```

Stream the large configs instead of downloading them:

```python
dockerfiles = load_dataset(
    "Helmcode/stack-v3-devops", "dockerfile", split="train", streaming=True
)
hardened = (row for row in dockerfiles if row["flags"]["pins_digest"])
```

Reconstruct a unit as files on disk, which is how you feed it to `helm lint`,
`terraform validate` or `hadolint`:

```python
import pathlib

def materialise(row, root):
    prefix = row["unit_prefix"]
    for entry in row["files"]:
        relative = entry["path"][len(prefix):].lstrip("/") if prefix else entry["path"]
        target = pathlib.Path(root, relative or pathlib.Path(entry["path"]).name)
        target.parent.mkdir(parents=True, exist_ok=True)
        target.write_text(entry["content"])

materialise(charts[0], "/tmp/chart")
```

Restrict to units whose every file carries a permissive license header, but read
the licensing section first, because that is not the same as permissively
licensed code:

```python
permissive = charts.filter(lambda row: row["flags"]["all_permissive"])
```

### What it is good for

- **Evaluation.** Complete, self-contained units are what an executable benchmark
  needs: render the chart, validate the module, lint the Dockerfile, and score on
  whether real tools accept the output.
- **Fine-tuning on infrastructure tasks**, where the unit boundary matters more
  than the file: a model that writes one template without `values.yaml` has not
  written a chart.
- **Measuring practice.** The flags make questions like "what share of public
  Dockerfiles run as root" answerable in one pass instead of a research project.

It is **not** a pretraining corpus. 5.4 GB is small, and the classes are
deliberately unbalanced towards what exists rather than what would balance nicely.

## Schema

Every config shares a base schema and adds its own `quality` and `flags` structs.

| Field | Type | Notes |
|---|---|---|
| `unit_type` | string | one of the seven config names |
| `repo_path` | string | `owner/name`, for attribution |
| `commit_id` | string | the exact commit the files came from |
| `stars` | int32 | GitHub stars at crawl time |
| `unit_prefix` | string | directory the unit was rooted at, `""` for repo root |
| `shard` | int32 | source shard, for reproducibility |
| `license_types` | list\<string\> | distinct `license_type` values across the unit's files |
| `files` | list\<struct\> | `path`, `content`, `license_type`, `detected_licenses`, `size_bytes` |
| `quality` | struct | per class: template counts, stage counts, service counts, manifest kinds |
| `flags` | struct | derived booleans, below |

Flags worth knowing about:

- `self_contained` (helm_chart): the chart does not call a helper it lacks.
  **72.9%** of charts qualify; the rest cannot be rendered
  by `helm template` on their own.
- `pins_digest` / `uses_latest_tag` (dockerfile): supply-chain hygiene.
- `has_unpinned_action` (workflow): actions referenced by tag or branch instead of
  a commit SHA.
- `all_permissive`: every file in the unit is labelled `permissive`. Read the
  licensing section before relying on this.

## What this corpus says about real-world infrastructure

Measured across every unit, not a sample:

- **89.0% of Dockerfiles set no `USER`**, so the container runs as root
- **98.6% of Dockerfiles declare no `HEALTHCHECK`**
- **89.5% of workflows declare no `permissions`**, inheriting the default token scope
- **91.1% of Compose files define no healthcheck**
- 20.3% of Dockerfiles are multi-stage
- Top Kubernetes kinds: Deployment, Service, Kustomization, ConfigMap, Ingress

That is the baseline any model trained on public infrastructure code will imitate,
which is the point of publishing it as a measurable corpus rather than a curated
showcase.

## Provenance and how it was built

Built with [helmcode/stack-slice](https://github.com/helmcode/stack-slice)
(Apache-2.0). The corpus was surveyed and extracted **without downloading the
4.71 TB dataset**: `content` is 96.9% of every shard, so a metadata-only pass
costs 1% of the bytes, and extraction streams shards over HTTP range requests
without ever storing one.

- Source revision: **`de81e3ca7151`** of `HuggingFaceCode/stack-v3-train`
- Shards swept: **8,196 of 8,196**, covering 157.9M repositories
- Forks skipped, so units come from the repository that authored them
- Re-filtered for opt-out on **2026-07-28**, against the revision then at
  HEAD (`d7bc7991ea32`), and verified equivalent to the current HEAD
  `2b4797afd567` (see Licensing)

Gates are content-based, never popularity-based: a chart must have parseable
metadata, two or more templates and actual templating; a Terraform module must
declare real blocks and not be machine-generated; an Ansible role must have a task
list a parser accepts; a manifest set must have two or more manifests that load.

## Licensing, and a finding you should not skip

This dataset is released under **ODC-By 1.0**, inherited from The Stack v3.
**The code inside remains under its original licenses**, and `repo_path` plus
`commit_id` are included on every unit precisely so attribution is possible.

**The `license_type` labels are header-based, not repository-based.** In the source
corpus only 3.41% of files are labelled `permissive` and 98.2% of repositories
contain none at all. Apache-2.0 is detected 26,624 times against MIT's 442, which
inverts their real popularity on GitHub: the Apache convention puts a license
header in every source file, while MIT projects ship a single root `LICENSE`. So
`license_type == permissive` means **"this file carries an inline license header"**,
not "this file comes from a permissively licensed project".

Two consequences:

1. Filtering to `permissive` does not give you a representative permissive
   corpus, it gives you an Apache-2.0-skewed slice.
2. The remaining `no_license` majority is code with **no license grant at all**,
   not code that is permissively licensed. Treat it accordingly.

The repository-level license cannot be recovered from within The Stack v3 either:
plain-text `LICENSE` files were dropped by its quality filter, so only 8 of 20,923
repositories in a sample shard ship one. A provably permissive subset needs
external enrichment keyed on `repo_path`.

**Opt-out.** Upstream applies opt-out removals in place and re-uploads. This
dataset was re-filtered by `repo_path` on 2026-07-28, dropping
9,439 units whose repositories had been removed.

Be aware that **upstream rewrites its own history**: the revision we filtered
against (`d7bc7991ea32`) was squashed away within a day, so source SHAs are
not durable references. We therefore record the filter date alongside the SHA, and
re-verify against the current HEAD (`2b4797afd567`) rather than assuming an
old SHA still resolves. If you find your code
here, check inclusion in the source corpus with the
[Am I in The Stack?](https://huggingface.co/spaces/HuggingFaceCode/in-the-stack)
Space and submit a removal request following the
[opt-out instructions](https://github.com/bigcode-project/opt-out-v2); we
re-filter on each upstream patch release.

## Known limitations

- **The source corpus repeats file rows inside a repository**: 10.4% of
  repositories and 14.5% of all file rows, byte-identical by `content_id`. This
  dataset deduplicates by (path, content), removing 2,166,221 repeated
  files, and then **drops the 122,886 units that only met their
  gate because of that repetition** (a "set of two manifests" whose two manifests
  were the same file is not a set of two). Counts here are therefore lower than a
  naive extraction would report, and correctly so. Quality counters such as
  `templates`, `tf_files` and `manifests` are recomputed after deduplication, so
  they describe the files actually present.
- **27.1% of Helm charts cannot render standalone**
  because they call helpers they do not carry. Filter on `self_contained`.
- **Ansible precision is a floor, not a measurement.** "A list of mappings with
  Ansible-ish keys" also matches ordinary YAML lists, and role variable files are
  indistinguishable from any other mapping by content alone.
- `manifest_set` groups manifests by directory, which is a convention, not a
  deployment boundary.
- Stars are as of the crawl and 58-76% of units come from repositories with none.
  Popularity was deliberately not used as a gate; see the card's reasoning above.

## Updates and versioning

Upstream applies opt-out removals in place, re-uploads the whole dataset and
squashes its history, which means the source moves and old revision SHAs stop
resolving. This dataset therefore records both the revision it was
extracted from and the revision it was last compliance-filtered against, and both
appear above. When upstream ships a patch release we re-filter and push a new
version; the extraction itself is not repeated unless the tooling changes.

If you need byte-for-byte reproducibility, pin the dataset revision you loaded.

## Reproducing this dataset

Everything here was produced by [helmcode/stack-slice](https://github.com/helmcode/stack-slice):

```bash
# Survey the corpus for 179 MB of transfer, no download
python -m stackslice.scan --shards 24

# Score the classifier against an independent YAML parser
python -m stackslice.measure --shards 3

# Sweep and extract units (streams shards, stores nothing but output)
python -m stackslice.extract --shards 8196 --workers 12 --out units

# Re-filter for opt-out, deduplicate, add flags
python -m stackslice.finalize units --out units_final \
    --revision <target-revision> --uuid <shard-uuid>

# Convert to parquet, one config per class
python -m stackslice.publish units_final --out dataset
```

The full measurement record, including the findings quoted in this card, is in
[FINDINGS.md](https://github.com/helmcode/stack-slice/blob/main/FINDINGS.md).

## Citation

```bibtex
@misc{stack_v3_devops,
  title  = {The Stack v3 DevOps Corpus},
  author = {Helmcode},
  year   = {2026},
  url    = {https://huggingface.co/datasets/Helmcode/stack-v3-devops},
  note   = {Extracted from The Stack v3 with helmcode/stack-slice}
}
```

Please also cite the source corpus, [The Stack v3](https://huggingface.co/datasets/HuggingFaceCode/stack-v3-train).