Files
upstream-rocketchat/.github/actions/docker-image-size-tracker/action.yml
upstream-mirror 30810eecd7
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:21:02 +05:00

582 lines
21 KiB
YAML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: 'Docker Image Size Tracker'
description: 'Track and report Docker image sizes in Pull Requests'
author: 'Rocket.Chat'
inputs:
github-token:
description: 'GitHub token for commenting on PRs'
required: true
ci-pat:
description: 'GitHub token for committing to history branch'
required: true
registry:
description: 'Container registry (e.g., ghcr.io)'
required: false
default: 'ghcr.io'
repository:
description: 'Repository name (e.g., rocketchat)'
required: true
tag:
description: 'Image tag to measure'
required: true
baseline-tag:
description: 'Baseline tag to compare against'
required: false
default: 'develop'
platform:
description: 'Platform architecture to compare (amd64 or arm64)'
required: false
default: 'amd64'
size-thresholds:
description: 'Optional JSON: per-image trigger thresholds. Only comment when an image increase exceeds its threshold. Example: {"rocketchat":{"mb":50,"percent":5},"omnichannel":{"mb":10,"percent":2}}'
required: false
default: ''
fail-thresholds:
description: 'Optional JSON: per-image fail thresholds. Task fails when an image increase exceeds its threshold (mb and/or percent). Example: {"rocketchat":{"mb":100,"percent":15}}'
required: false
default: ''
outputs:
total-size:
description: 'Total size in bytes'
value: ${{ steps.measure.outputs.total-size }}
size-diff:
description: 'Size difference in bytes'
value: ${{ steps.compare.outputs.size-diff }}
size-diff-percent:
description: 'Size difference percentage'
value: ${{ steps.compare.outputs.size-diff-percent }}
comment-triggered:
description: 'Whether to post PR comment (only when size is bigger and thresholds met)'
value: ${{ steps.compare.outputs.comment-triggered }}
failed:
description: 'True if image size exceeded fail-thresholds'
value: ${{ steps.compare.outputs.failed }}
runs:
using: 'composite'
steps:
- name: Download manifests
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with:
pattern: manifests-*
path: /tmp/manifests
merge-multiple: true
- name: Measure image sizes from artifacts
id: measure
shell: bash
env:
PLATFORM: ${{ inputs.platform }}
TAG: ${{ inputs.tag }}
run: |
echo "Reading image sizes from build artifacts for platform: $PLATFORM"
declare -A sizes
declare -a services_list
total=0
# Loop through service directories (same as publish workflow)
shopt -s nullglob
for service_dir in /tmp/manifests/*; do
[[ -d "$service_dir" ]] || continue
service="$(basename "$service_dir")"
echo "Processing service: $service"
services_list+=("$service")
size=0
# Read only the specified platform architecture
manifest_file="$service_dir/$PLATFORM/manifest.json"
if [[ -f "$manifest_file" ]]; then
# docker manifest inspect -v puts the manifest under SchemaV2Manifest
# (Docker schema2 images) or OCIManifest (OCI images, which buildx
# produces by default). Coalesce so both formats are read.
config_size=$(jq -r '(.SchemaV2Manifest // .OCIManifest).config.size // 0' "$manifest_file")
layers_size=$(jq '[(.SchemaV2Manifest // .OCIManifest).layers[]?.size // 0] | add // 0' "$manifest_file")
size=$((config_size + layers_size))
echo " → Found $manifest_file: $size bytes (config: $config_size, layers: $layers_size)"
else
echo " ⚠ Manifest not found for platform $PLATFORM: $manifest_file"
fi
sizes[$service]=$size
total=$((total + size))
done
echo "Total size (all services, $PLATFORM only): $total bytes"
echo "total-size=$total" >> $GITHUB_OUTPUT
# Save to JSON
echo "{" > current-sizes.json
echo " \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"," >> current-sizes.json
echo " \"tag\": \"$TAG\"," >> current-sizes.json
echo " \"total\": $total," >> current-sizes.json
echo " \"services\": {" >> current-sizes.json
first=true
for service in "${services_list[@]}"; do
if [[ "$first" == "true" ]]; then
first=false
else
echo "," >> current-sizes.json
fi
echo " \"$service\": ${sizes[$service]}" >> current-sizes.json
done
echo " }" >> current-sizes.json
echo "}" >> current-sizes.json
echo "Current sizes saved:"
cat current-sizes.json
# Save services list for baseline measurement
printf '%s\n' "${services_list[@]}" > /tmp/services-list.txt
- name: Measure baseline
id: baseline
shell: bash
continue-on-error: true
env:
REGISTRY: ${{ inputs.registry }}
ORG: ${{ inputs.repository }}
TAG: ${{ inputs.baseline-tag }}
PLATFORM: ${{ inputs.platform }}
run: |
echo "Measuring baseline: $REGISTRY/$ORG/*:$TAG (platform: $PLATFORM)"
declare -A sizes
declare -a services_list
total=0
# Read services list from current measurement
while IFS= read -r service; do
services_list+=("$service")
# Map service name to image name (handle rocketchat -> rocket.chat)
if [[ "$service" == "rocketchat" ]] || [[ "$service" == "rocketchat-cov" ]]; then
image_name="rocket.chat"
[[ "$service" == "rocketchat-cov" ]] && image_name="rocket.chat-cov"
else
image_name="$service"
fi
image="$REGISTRY/$ORG/$image_name:$TAG"
echo " → Inspecting $image"
size=0
if manifest=$(docker manifest inspect "$image" 2>/dev/null); then
# Check if it's a manifest list (multi-arch)
if echo "$manifest" | jq -e '.manifests' > /dev/null 2>&1; then
# Manifest list - find the specified platform
echo " → Multi-arch manifest detected, filtering for $PLATFORM"
platform_digest=$(echo "$manifest" | jq -r --arg arch "$PLATFORM" '.manifests[] | select(.platform.architecture == $arch) | .digest' | head -1)
if [[ -n "$platform_digest" ]]; then
echo " → Inspecting $PLATFORM platform: $platform_digest"
if platform_manifest=$(docker manifest inspect "$REGISTRY/$ORG/$image_name@$platform_digest" 2>/dev/null); then
config_size=$(echo "$platform_manifest" | jq -r '.config.size // 0')
layers_size=$(echo "$platform_manifest" | jq '[.layers[]?.size // 0] | add // 0')
size=$((config_size + layers_size))
echo " → Size: $size bytes"
fi
else
echo " ⚠ Platform $PLATFORM not found in manifest"
fi
else
# Single arch manifest
config_size=$(echo "$manifest" | jq -r '.config.size // 0')
layers_size=$(echo "$manifest" | jq '[.layers[]?.size // 0] | add // 0')
size=$((config_size + layers_size))
fi
fi
sizes[$service]=$size
total=$((total + size))
done < /tmp/services-list.txt
echo "baseline-total=$total" >> $GITHUB_OUTPUT
echo "{" > baseline-sizes.json
echo " \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"," >> baseline-sizes.json
echo " \"tag\": \"$TAG\"," >> baseline-sizes.json
echo " \"total\": $total," >> baseline-sizes.json
echo " \"services\": {" >> baseline-sizes.json
first=true
for service in "${services_list[@]}"; do
if [[ "$first" == "true" ]]; then
first=false
else
echo "," >> baseline-sizes.json
fi
echo " \"$service\": ${sizes[$service]}" >> baseline-sizes.json
done
echo " }" >> baseline-sizes.json
echo "}" >> baseline-sizes.json
- name: Setup history storage
id: history
shell: bash
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
# Create a separate worktree for history branch
mkdir -p /tmp/history-worktree
# Try to fetch history branch
if git ls-remote --heads origin image-size-history | grep -q image-size-history; then
git fetch origin image-size-history
git worktree add /tmp/history-worktree image-size-history
else
# Create orphan branch for history in worktree
git worktree add --detach /tmp/history-worktree
cd /tmp/history-worktree
git checkout --orphan image-size-history
git rm -rf . 2>/dev/null || true
mkdir -p history
echo "# Image Size History" > README.md
echo "This branch stores historical image size data for tracking" >> README.md
git add README.md
git commit -m "Initialize image size history"
git push origin image-size-history
cd -
fi
mkdir -p /tmp/history-worktree/history
- name: Load historical data
shell: bash
run: |
# Load last 30 measurements and group by day (keep only last entry per day)
echo "[]" > history-data.json
if [[ -d /tmp/history-worktree/history ]]; then
jq -s '.' /tmp/history-worktree/history/*.json 2>/dev/null | jq '
sort_by(.timestamp) |
group_by(.timestamp[0:10]) |
map(.[-1]) |
.[-30:]
' > history-data.json || echo "[]" > history-data.json
fi
count=$(jq 'length' history-data.json)
echo "Loaded $count historical measurements (one per day)"
- name: Save current measurement to history
if: github.ref == 'refs/heads/develop'
shell: bash
env:
CI_PAT: ${{ inputs.ci-pat }}
run: |
timestamp=$(date -u +%Y%m%d-%H%M%S)
commit_sha="${{ github.sha }}"
# Add commit info to current measurement
jq --arg sha "$commit_sha" '. + {commit: $sha}' current-sizes.json > "/tmp/history-worktree/history/${timestamp}.json"
cd /tmp/history-worktree
git add "history/${timestamp}.json"
git commit -m "Add measurement for ${timestamp} (${commit_sha:0:7})"
git config --global user.email "ci@rocket.chat"
git config --global user.name "rocketchat-ci[bot]"
git config --global url.https://$CI_PAT@github.com/.insteadOf https://github.com/
git push origin image-size-history
cd -
echo "Saved measurement to history"
- name: Compare and generate report
id: compare
shell: bash
env:
SIZE_THRESHOLDS: ${{ inputs.size-thresholds }}
FAIL_THRESHOLDS: ${{ inputs.fail-thresholds }}
TAG: ${{ inputs.tag }}
BASELINE_TAG: ${{ inputs.baseline-tag }}
run: |
current_total=$(jq -r '.total' current-sizes.json)
if [[ ! -f baseline-sizes.json ]]; then
echo "No baseline available"
echo "size-diff=0" >> $GITHUB_OUTPUT
echo "size-diff-percent=0" >> $GITHUB_OUTPUT
echo "comment-triggered=false" >> $GITHUB_OUTPUT
cat > report.md << 'EOF'
# 📦 Docker Image Size Report
**Status:** First measurement - no baseline for comparison
**Total Size:** $(numfmt --to=iec-i --suffix=B $current_total)
EOF
exit 0
fi
baseline_total=$(jq -r '.total' baseline-sizes.json)
diff=$((current_total - baseline_total))
if [[ $baseline_total -gt 0 ]]; then
percent=$(awk "BEGIN {printf \"%.2f\", ($diff / $baseline_total) * 100}")
else
percent=0
fi
echo "size-diff=$diff" >> $GITHUB_OUTPUT
echo "size-diff-percent=$percent" >> $GITHUB_OUTPUT
# Only comment when size is bigger than baseline; optionally require per-image thresholds
THRESHOLDS="$SIZE_THRESHOLDS"
FAIL_THRESHOLDS="$FAIL_THRESHOLDS"
comment_triggered=false
fail_triggered=false
if [[ $diff -gt 0 ]]; then
if [[ -z "$THRESHOLDS" ]] || [[ "$THRESHOLDS" == "{}" ]]; then
comment_triggered=true
fi
fi
color="gray"
if (( $(awk "BEGIN {print ($percent > 0.01)}") )); then
color="red"
elif (( $(awk "BEGIN {print ($percent < -0.01)}") )); then
color="green"
fi
# Generate report
if [[ $diff -gt 0 ]]; then
emoji="📈"
badge="![](https://img.shields.io/badge/size-+${percent}%25-${color})"
sign="+"
elif [[ $diff -lt 0 ]]; then
emoji="📉"
badge="![](https://img.shields.io/badge/size--${percent}%25-${color})"
sign="-"
else
emoji="➡️"
badge="![](https://img.shields.io/badge/size-unchanged-${color})"
sign=""
fi
cat > report.md << EOF
# 📦 Docker Image Size Report
## $emoji Changes $badge
| Service | Current | Baseline | Change | Percent |
|---------|---------|----------|--------|---------|
| **sum of all images** | **$(numfmt --to=iec-i --suffix=B $current_total)** | **$(numfmt --to=iec-i --suffix=B $baseline_total)** | **${sign}$(numfmt --to=iec-i --suffix=B ${diff#-})** | $badge |
EOF
# Get services dynamically from current-sizes.json, sorted by size (largest first)
for service in $(jq -r '.services | to_entries | sort_by(-.value) | .[].key' current-sizes.json); do
current=$(jq -r ".services.\"$service\"" current-sizes.json)
baseline=$(jq -r ".services.\"$service\" // 0" baseline-sizes.json)
service_diff=$((current - baseline))
if [[ $baseline -gt 0 ]]; then
service_percent=$(awk "BEGIN {printf \"%.2f\", ($service_diff / $baseline) * 100}")
else
service_percent=0
fi
# Check per-image thresholds for comment trigger (only when size increased)
if [[ $diff -gt 0 ]] && [[ -n "$THRESHOLDS" ]] && [[ "$THRESHOLDS" != "{}" ]]; then
threshold_mb=$(echo "$THRESHOLDS" | jq -r ".\"$service\".mb // empty")
threshold_pct=$(echo "$THRESHOLDS" | jq -r ".\"$service\".percent // empty")
if [[ -n "$threshold_mb" ]] || [[ -n "$threshold_pct" ]]; then
exceeded=false
if [[ -n "$threshold_mb" ]] && [[ $service_diff -ge $(awk "BEGIN {printf \"%.0f\", $threshold_mb * 1048576}") ]]; then
exceeded=true
fi
if [[ -n "$threshold_pct" ]] && [[ $service_percent != "0.00" ]] && (( $(awk "BEGIN {print ($service_percent >= $threshold_pct)}") )); then
exceeded=true
fi
[[ "$exceeded" == "true" ]] && comment_triggered=true
fi
fi
# Check per-image fail thresholds (task fails when exceeded)
if [[ $diff -gt 0 ]] && [[ -n "$FAIL_THRESHOLDS" ]] && [[ "$FAIL_THRESHOLDS" != "{}" ]]; then
fail_mb=$(echo "$FAIL_THRESHOLDS" | jq -r ".\"$service\".mb // empty")
fail_pct=$(echo "$FAIL_THRESHOLDS" | jq -r ".\"$service\".percent // empty")
if [[ -n "$fail_mb" ]] || [[ -n "$fail_pct" ]]; then
exceeded=false
if [[ -n "$fail_mb" ]] && [[ $service_diff -ge $(awk "BEGIN {printf \"%.0f\", $fail_mb * 1048576}") ]]; then
exceeded=true
fi
if [[ -n "$fail_pct" ]] && [[ $service_percent != "0.00" ]] && (( $(awk "BEGIN {print ($service_percent >= $fail_pct)}") )); then
exceeded=true
fi
[[ "$exceeded" == "true" ]] && fail_triggered=true
fi
fi
color="gray"
if (( $(awk "BEGIN {print ($service_percent > 0.01)}") )); then
color="red"
elif (( $(awk "BEGIN {print ($service_percent < -0.01)}") )); then
color="green"
fi
if [[ $service_diff -gt 0 ]]; then
badge="![](https://img.shields.io/badge/size-+${service_percent}%25-${color})"
sign="+"
elif [[ $service_diff -lt 0 ]]; then
badge="![](https://img.shields.io/badge/size--${service_percent}%25-${color})"
sign="-"
else
badge="![](https://img.shields.io/badge/size-unchanged-${color})"
sign=""
fi
echo "| $service | $(numfmt --to=iec-i --suffix=B $current) | $(numfmt --to=iec-i --suffix=B $baseline) | ${sign}$(numfmt --to=iec-i --suffix=B ${service_diff#-}) | $badge |" >> report.md
done
# Generate historical trend chart
echo "" >> report.md
echo "## 📊 Historical Trend" >> report.md
echo "" >> report.md
# Load history and generate mermaid chart
history_count=$(jq 'length' history-data.json)
if [[ $history_count -gt 0 ]]; then
# Get all services from current build
all_services=$(jq -r '.services | keys | .[]' current-sizes.json | sort)
# Generate Mermaid chart data
x_labels=""
declare -A service_data
# Initialize service data arrays
for service in $all_services; do
service_data[$service]=""
done
# Process historical data
while IFS= read -r line; do
timestamp=$(echo "$line" | jq -r '.timestamp')
date_label=$(date -d "$timestamp" +"%m/%d %H:%M")
if [[ -z "$x_labels" ]]; then
x_labels="\"$date_label\""
else
x_labels="$x_labels, \"$date_label\""
fi
# Add data point for each service
for service in $all_services; do
size=$(echo "$line" | jq -r ".services.\"$service\" // 0")
size_gb=$(awk "BEGIN {printf \"%.2f\", $size / 1073741824}")
if [[ -z "${service_data[$service]}" ]]; then
service_data[$service]="$size_gb"
else
service_data[$service]="${service_data[$service]}, $size_gb"
fi
done
done < <(jq -c '.[]' history-data.json)
# Add current PR as last point
current_date=$(date -u +"%m/%d %H:%M")
x_labels="$x_labels, \"$current_date (PR)\""
for service in $all_services; do
size=$(jq -r ".services.\"$service\" // 0" current-sizes.json)
size_gb=$(awk "BEGIN {printf \"%.2f\", $size / 1073741824}")
service_data[$service]="${service_data[$service]}, $size_gb"
done
# Generate mermaid chart with multiple lines
cat >> report.md << EOF
\`\`\`mermaid
---
config:
theme: "dark"
xyChart:
width: 900
height: 400
---
xychart
title "Image Size Evolution by Service (Last 30 Days + This PR)"
x-axis [$x_labels]
y-axis "Size (GB)" 0 --> 0.5
EOF
# Add a line for each service
for service in $all_services; do
echo " line \"$service\" [${service_data[$service]}]" >> report.md
done
cat >> report.md << 'EOF'
```
EOF
# Add summary stats
min_size=$(jq '[.[].total] | min' history-data.json)
max_size=$(jq '[.[].total] | max' history-data.json)
avg_size=$(jq '[.[].total] | add / length' history-data.json)
cat >> report.md << EOF
**Statistics (last $history_count days):**
- 📊 Average: $(numfmt --to=iec-i --suffix=B ${avg_size%.*})
- ⬇️ Minimum: $(numfmt --to=iec-i --suffix=B $min_size)
- ⬆️ Maximum: $(numfmt --to=iec-i --suffix=B $max_size)
- 🎯 Current PR: $(numfmt --to=iec-i --suffix=B $current_total)
EOF
else
cat >> report.md << 'EOF'
*No historical data available yet. History tracking starts after merging to develop.*
EOF
fi
cat >> report.md << EOF
<details>
<summary>ℹ️ About this report</summary>
This report compares Docker image sizes from this build against the \`$BASELINE_TAG\` baseline.
- **Tag:** \`$TAG\`
- **Baseline:** \`$BASELINE_TAG\`
- **Timestamp:** $(date -u +"%Y-%m-%d %H:%M:%S UTC")
- **Historical data points:** $history_count
</details>
EOF
echo "comment-triggered=$comment_triggered" >> $GITHUB_OUTPUT
echo "failed=$fail_triggered" >> $GITHUB_OUTPUT
if [[ "$fail_triggered" == "true" ]]; then
echo "::error::Docker image size exceeded fail-thresholds (mb/percent). Check the report for details."
exit 1
fi
- name: Add report to Job Summary
if: always()
shell: bash
run: |
if [[ -f report.md ]]; then
cat report.md >> $GITHUB_STEP_SUMMARY
fi
- name: Cleanup worktree
if: always()
shell: bash
run: |
if [[ -d /tmp/history-worktree ]]; then
git worktree remove /tmp/history-worktree --force 2>/dev/null || true
fi
branding:
icon: 'package'
color: 'blue'