Skip to content

Commit b7b30f8

Browse files
committed
Add files to publish oci artifact
Signed-off-by: Manuel Morejon <manuel@mmorejon.io>
1 parent 60dd81a commit b7b30f8

File tree

10 files changed

+377
-1
lines changed

10 files changed

+377
-1
lines changed

.github/workflows/oci-publish.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Publish OCI Artifact
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
7+
env:
8+
REGISTRY: ghcr.io
9+
10+
jobs:
11+
publish-artifact:
12+
runs-on: ubuntu-latest
13+
permissions:
14+
contents: read
15+
packages: write
16+
17+
steps:
18+
# 1. Download the repository code
19+
- name: Checkout repository
20+
uses: actions/checkout@v4
21+
22+
# 2. Configure ORAS CLI (Using the official action)
23+
# https://github.com/marketplace/actions/setup-oras
24+
- name: Set up ORAS
25+
uses: oras-project/setup-oras@v1
26+
with:
27+
version: 1.3.0
28+
29+
# 3. Install JQ (Required to manipulate the JSON in the script)
30+
- name: Install JQ (JSON Processor)
31+
run: sudo apt-get update && sudo apt-get install -y jq
32+
33+
# 4. Login to GitHub Container Registry
34+
- name: Log into GHCR
35+
run: echo "${{ secrets.GITHUB_TOKEN }}" | oras login ${{ env.REGISTRY }} -u ${{ github.actor }} --password-stdin
36+
37+
# 5. Execute the publish script
38+
# The script will automatically read GITHUB_REPOSITORY, GITHUB_SHA, etc.
39+
- name: Execute Publish Script
40+
run: |
41+
chmod +x publish.sh
42+
./publish.sh

README.en.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# erase-una-vez-5
2+
3+
[![es](https://img.shields.io/badge/Leer_en-Español-blue.svg?style=flat-square)](README.md)
4+
5+
<div align="center">
6+
7+
<img src="./assets/book-cover.jpg" alt="Once Upon a Time Kubernetes Book Cover" width="300"/>
8+
9+
### 📚 OCI Data Volumes
10+
11+
Their function is to generate **"Data-Only" Container Images**: images that do not have an operating system or run anything; they simply serve as a "pen drive" to transport your files to any Kubernetes cluster or Docker environment.
12+
13+
This repository is a practical example created for the book **"Érase una vez Kubernetes"**.
14+
15+
👇 **Get the updated 2025 edition here:** 👇
16+
17+
[![Amazon](https://img.shields.io/badge/Amazon-Buy_Paperback-orange?style=for-the-badge&logo=amazon)](https://www.amazon.es/dp/B0F9VPCJ7X)
18+
[![LeanPub](https://img.shields.io/badge/LeanPub-Download_Ebook-blue?style=for-the-badge&logo=leanpub)](https://leanpub.com/once-upon-a-time-kubernetes)
19+
20+
</div>
21+
22+
## ⚡ What makes it special?
23+
24+
* **No Docker:** It is built using pure scripts (ORAS + Bash). You do not need anything else.
25+
* **Ultralight:** Based on `scratch`. The image size is equal to the size of your files.
26+
* **Multi-Architecture:** It works the same on Intel servers (`amd64`) as on Raspberry Pi/Mac (`arm64`).
27+
* **Automatic:** GitHub Actions automatically injects the version, author, and commit.
28+
29+
---
30+
31+
## 📦 How to consume the data
32+
33+
Since this image has no operating system, you cannot run `docker run`. Use it to copy data to other applications:
34+
35+
### Example in a Dockerfile:
36+
37+
Create a container image that uses the data image as an intermediate layer.
38+
39+
```dockerfile
40+
# 1. Pull our data
41+
FROM ghcr.io/mmorejon/erase-una-vez-5:v1 AS my-files
42+
43+
# 2. Copy our files to our real application (e.g. Nginx)
44+
FROM nginx:alpine
45+
COPY --from=my-files / /usr/share/nginx/html/
46+
```
47+
48+
Build the image and start the container.
49+
50+
```bash
51+
docker image build --tag server . && \
52+
docker container run --detach --publish 8080:80 server
53+
```
54+
55+
Access the files included in the image.
56+
57+
```bash
58+
curl http://localhost:8080/example-1.txt
59+
> Érase una vez Kubernetes
60+
```
61+
62+
### Example in Kubernetes:
63+
64+
Create a pod that uses the data image as a volume. To use this example, you need a Kubernetes 1.35+ cluster.
65+
66+
If you do not have a Kubernetes cluster, you can use the exercise repository from the book "Érase una vez Kubernetes".
67+
68+
<https://github.com/mmorejon/erase-una-vez-k8s>
69+
70+
```yaml
71+
apiVersion: v1
72+
kind: Pod
73+
metadata:
74+
name: erase-una-vez-5
75+
spec:
76+
containers:
77+
- name: server
78+
image: nginx:alpine
79+
volumeMounts:
80+
- name: volume
81+
mountPath: /usr/share/nginx/html/
82+
volumes:
83+
- name: volume
84+
image:
85+
reference: ghcr.io/mmorejon/erase-una-vez-5:main
86+
pullPolicy: IfNotPresent
87+
```
88+
89+
---
90+
91+
## 🤝 Community and Feedback
92+
93+
1. ⭐ **Has this been useful to you?** Give a **star** to the repository (top right). It helps us reach more engineers.
94+
2. 📚 **Still don't have the book?** Buy the book on Amazon or Leanpub.
95+
96+
<div align="center">
97+
<a href="https://www.amazon.es/dp/B0F9VPCJ7X">
98+
<img src="https://img.shields.io/badge/Amazon-See_price_and_reviews-orange?style=for-the-badge&logo=amazon" />
99+
</a>
100+
</div>

README.md

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,100 @@
1-
# erase-una-vez-5
1+
# erase-una-vez-5
2+
3+
[![English](https://img.shields.io/badge/Read_in-English-blue?style=flat-square)](README.en.md)
4+
5+
<div align="center">
6+
7+
<img src="./assets/book-cover.jpg" alt="Portada Libro Érase una vez Kubernetes" width="300"/>
8+
9+
### 📚 Volúmenes OCI de Datos
10+
11+
Su función es generar **Imágenes de Contenedor "Data-Only"**: imágenes que no tienen sistema operativo ni ejecutan nada, simplemente sirven como un "pendrive" para transportar tus archivos a cualquier clúster de Kubernetes o entorno Docker.
12+
13+
Este repositorio es un ejemplo práctico creado para el libro **"Érase una vez Kubernetes"**.
14+
15+
👇 **Consigue la edición actualizada 2025 aquí:** 👇
16+
17+
[![Amazon](https://img.shields.io/badge/Amazon-Comprar_en_Tapa_Blanda-orange?style=for-the-badge&logo=amazon)](https://www.amazon.es/dp/8409212765)
18+
[![LeanPub](https://img.shields.io/badge/LeanPub-Descargar_Ebook-blue?style=for-the-badge&logo=leanpub)](https://leanpub.com/erase-una-vez-kubernetes)
19+
20+
</div>
21+
22+
## ⚡ ¿Qué tiene de especial?
23+
24+
* **Sin Docker:** Se construye usando scripts puros (ORAS + Bash). No necesitas nada más.
25+
* **Ultraligera:** Basada en `scratch`. El tamaño de la imagen es igual al tamaño de tus archivos.
26+
* **Multi-Arquitectura:** Funciona igual en servidores Intel (`amd64`) que en Raspberry Pi/Mac (`arm64`).
27+
* **Automática:** GitHub Actions inyecta la versión, el autor y el commit automáticamente.
28+
29+
---
30+
31+
## 📦 Cómo consumir los datos
32+
33+
Como esta imagen no tiene sistema operativo, no puedes hacer `docker run`. Úsala para copiar datos a otras aplicaciones:
34+
35+
### Ejemplo en un Dockerfile:
36+
37+
Crea una imagen de contenedor que utilice la imagen de datos como capa intermedia.
38+
39+
```dockerfile
40+
# 1. Pull our data
41+
FROM ghcr.io/mmorejon/erase-una-vez-5:v1 AS my-files
42+
43+
# 2. Copy our files to our real application (e.g. Nginx)
44+
FROM nginx:alpine
45+
COPY --from=my-files / /usr/share/nginx/html/
46+
```
47+
48+
Construye la imagen e inicia el contenedor.
49+
50+
```bash
51+
docker image build --tag server . && \
52+
docker container run --detach --publish 8080:80 server
53+
```
54+
55+
Accede a los ficheros incluidos en la imagen.
56+
57+
```bash
58+
curl http://localhost:8080/example-1.txt
59+
> Érase una vez Kubernetes
60+
```
61+
62+
### Ejemplo en Kubernetes:
63+
64+
Crea un pod que utilice la imagen de datos como volumen. Para utilizar este ejemplo necesitas tener un clúster de Kubernentes 1.35+.
65+
66+
Si no tienes un clúster de Kubernetes, puedes usar el repositorio de ejercicios del libro Érase una vez Kubernetes.
67+
68+
<https://github.com/mmorejon/erase-una-vez-k8s>
69+
70+
```yaml
71+
apiVersion: v1
72+
kind: Pod
73+
metadata:
74+
name: erase-una-vez-5
75+
spec:
76+
containers:
77+
- name: server
78+
image: nginx:alpine
79+
volumeMounts:
80+
- name: volume
81+
mountPath: /usr/share/nginx/html/
82+
volumes:
83+
- name: volume
84+
image:
85+
reference: ghcr.io/mmorejon/erase-una-vez-5:main
86+
pullPolicy: IfNotPresent
87+
```
88+
89+
---
90+
91+
## 🤝 Comunidad y Feedback
92+
93+
1. ⭐ **¿Te ha sido útil?** Dale una **estrella** al repositorio (arriba a la derecha). Nos ayuda a llegar a más ingenieros.
94+
2. 📚 **¿Aún no tienes el libro?** Compra el libro en Amazon o Leanpub.
95+
96+
<div align="center">
97+
<a href="https://www.amazon.es/dp/8409212765">
98+
<img src="https://img.shields.io/badge/Amazon-Ver_Precio_y_Opiniones-orange?style=for-the-badge&logo=amazon" />
99+
</a>
100+
</div>

assets/book-cover.jpg

324 KB
Loading

files/example-1.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Érase una vez Kubernetes

files/example-2.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ORAS was used to create the OCI artifact.

publish.sh

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#!/bin/bash
2+
set -e
3+
4+
# --- CONFIGURATION ---
5+
# Detect the repository name (user/repo)
6+
FULL_REPO="${GITHUB_REPOSITORY:-local/test-repo}"
7+
REPO_URL="https://github.com/${FULL_REPO}"
8+
IMAGE="ghcr.io/$FULL_REPO"
9+
TAG="${GITHUB_REF_NAME:-v1}"
10+
SRC_DIR="./files"
11+
# ---------------------
12+
13+
log_info() { echo -e "\033[1;34m[INFO]\033[0m $1"; }
14+
log_step() { echo -e "\033[1;33m[STEP $1]\033[0m $2"; }
15+
log_success() { echo -e "\033[1;32m[SUCCESS]\033[0m $1"; }
16+
17+
log_info "Initializing OCI artifact publication sequence."
18+
19+
# 0. Metadata Discovery
20+
log_step "0/5" "Gathering metadata from environment..."
21+
22+
# 1. Prepare the data layer
23+
log_step "1/5" "Packaging filesystem layer from: $SRC_DIR"
24+
tar -czf layer.tar.gz -C "$SRC_DIR" .
25+
26+
# Calculate the DiffID
27+
log_step "2/5" "Calculating data integrity checksums (DiffID)..."
28+
DIFF_ID=$(gzip -d -c layer.tar.gz | (sha256sum 2>/dev/null || shasum -a 256) | awk '{print $1}')
29+
30+
# Upload the layer
31+
log_step "3/5" "Uploading raw data blob..."
32+
LAYER_DESC=$(oras blob push "$IMAGE" layer.tar.gz --descriptor)
33+
34+
# --- BUILD FUNCTION ---
35+
build_arch() {
36+
local ARCH=$1
37+
echo " [INFO] Processing architecture: $ARCH" >&2
38+
39+
# A. Config Blob
40+
jq --arg arch "$ARCH" --arg diff "sha256:$DIFF_ID" \
41+
'.architecture = $arch | .rootfs.diff_ids[0] = $diff' \
42+
templates/config.json > "config-$ARCH.json"
43+
44+
local CFG_DESC=$(oras blob push "$IMAGE" "config-$ARCH.json" --descriptor)
45+
46+
# B. Manifest
47+
jq --argjson cfg "$CFG_DESC" \
48+
--argjson layer "$LAYER_DESC" \
49+
--arg src "$REPO_URL" \
50+
'.config.digest = $cfg.digest | .config.size = $cfg.size |
51+
.layers[0].digest = $layer.digest | .layers[0].size = $layer.size |
52+
.annotations["org.opencontainers.image.source"] = $src' \
53+
templates/manifest.json > "manifest-$ARCH.json"
54+
55+
oras manifest push "$IMAGE" "manifest-$ARCH.json" --descriptor
56+
}
57+
58+
# --- EXECUTION ---
59+
60+
log_step "4/5" "Building manifests..."
61+
AMD_DESC=$(build_arch "amd64")
62+
ARM_DESC=$(build_arch "arm64")
63+
64+
# Create Index
65+
log_step "5/5" "Constructing OCI Image Index..."
66+
jq --argjson amd "$AMD_DESC" \
67+
--argjson arm "$ARM_DESC" \
68+
--arg src "$REPO_URL" \
69+
'.manifests[0].digest = $amd.digest | .manifests[0].size = $amd.size |
70+
.manifests[1].digest = $arm.digest | .manifests[1].size = $arm.size |
71+
.annotations["org.opencontainers.image.source"] = $src' \
72+
templates/index.json > index_final.json
73+
74+
log_info "Publishing final tag: $IMAGE:$TAG"
75+
oras manifest push "$IMAGE:$TAG" index_final.json > /dev/null
76+
77+
# Cleanup
78+
rm layer.tar.gz config-*.json manifest-*.json index_final.json
79+
80+
echo ""
81+
log_success "Artifact published successfully."

templates/config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"architecture": "__ARCH__",
3+
"os": "linux",
4+
"rootfs": {
5+
"type": "layers",
6+
"diff_ids": ["sha256:__DIFF_ID__"]
7+
}
8+
}

templates/index.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"schemaVersion": 2,
3+
"mediaType": "application/vnd.oci.image.index.v1+json",
4+
"manifests": [
5+
{
6+
"mediaType": "application/vnd.oci.image.manifest.v1+json",
7+
"digest": "",
8+
"size": 0,
9+
"platform": {
10+
"architecture": "amd64",
11+
"os": "linux"
12+
}
13+
},
14+
{
15+
"mediaType": "application/vnd.oci.image.manifest.v1+json",
16+
"digest": "",
17+
"size": 0,
18+
"platform": {
19+
"architecture": "arm64",
20+
"os": "linux"
21+
}
22+
}
23+
],
24+
"annotations": {
25+
"org.opencontainers.image.source": ""
26+
}
27+
}

templates/manifest.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"schemaVersion": 2,
3+
"mediaType": "application/vnd.oci.image.manifest.v1+json",
4+
"config": {
5+
"mediaType": "application/vnd.oci.image.config.v1+json",
6+
"digest": "", "size": 0
7+
},
8+
"layers": [
9+
{
10+
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
11+
"digest": "", "size": 0
12+
}
13+
],
14+
"annotations": {
15+
"org.opencontainers.image.source": ""
16+
}
17+
}

0 commit comments

Comments
 (0)