Publishing via GitLab CI
If your team uses GitLab as the primary source of truth, you can set up a GitLab CI pipeline to automatically publish your function to the Aerostack Hub on every push to main or on a tagged release.
How It Works
Developer pushes to GitLab
│
▼
GitLab CI pipeline triggers
│
▼
Reads aerostack.json + source files
│
▼
Calls POST /api/community/functions/ci/publish
with your AEROSTACK_API_KEY CI variable
│
▼
Function is live on hub.aerostack.dev ✅No Aerostack-specific runner or plugin is required — the pipeline uses plain curl and jq which are available in any standard CI image.
Step-by-Step Setup
Get your Aerostack API key
Go to aerocall.ai/account/keys and create or copy an account key (prefix ac_).
Add the CI variable to GitLab
In your GitLab project:
- Go to Settings → CI/CD → Variables
- Click Add variable
- Key:
AEROSTACK_API_KEY - Value: your
ac_secret_xxxxkey - Type: Variable (not File)
- Enable Mask variable (so it’s hidden in logs)
- Optionally enable Protect variable to restrict it to protected branches only
Structure your repository
- .gitlab-ci.yml
- aerostack.json
- index.ts
- README.md
Add the pipeline file
Create .gitlab-ci.yml at the root of your function repository:
stages:
- publish
publish-to-aerostack:
stage: publish
image: node:20-alpine
before_script:
- apk add --no-cache curl jq
script:
- |
# Read aerostack.json
CONFIG=$(cat aerostack.json)
ENTRYPOINT=$(echo "$CONFIG" | jq -r '.entrypoint // "index.ts"')
# Read source file and escape for JSON
CODE=$(cat "$ENTRYPOINT" | jq -Rs .)
# Build payload: merge config + code + publish flag
PAYLOAD=$(jq -n \
--argjson config "$CONFIG" \
--argjson code "$CODE" \
'$config + {code: $code, publish: true}')
echo "Publishing $(echo "$CONFIG" | jq -r '.name') v$(echo "$CONFIG" | jq -r '.version')..."
# Call Aerostack publish API
RESPONSE=$(curl -s -w "\n%{http_code}" \
-X POST "https://api.aerocall.ai/api/community/functions/ci/publish" \
-H "X-API-Key: $AEROSTACK_API_KEY" \
-H "Content-Type: application/json" \
-d "$PAYLOAD")
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | head -1)
echo "Response: $BODY"
if [ "$HTTP_CODE" != "200" ] && [ "$HTTP_CODE" != "201" ]; then
echo "❌ Publish failed with HTTP $HTTP_CODE"
exit 1
fi
echo "✅ Published successfully!"
echo "$BODY" | jq .
only:
- mainFull Working Example
Complete .gitlab-ci.yml with staging validation + production publish, based on environment:
stages:
- validate
- publish
variables:
AEROSTACK_API: "https://api.aerocall.ai/api/community/functions/ci/publish"
# ── Validate on all feature branches ──────────────────────────────────────────
validate-function:
stage: validate
image: node:20-alpine
before_script:
- apk add --no-cache jq
script:
- |
echo "Validating aerostack.json..."
# Check required fields exist
for field in name version description category language entrypoint; do
VALUE=$(cat aerostack.json | jq -r ".$field")
if [ "$VALUE" = "null" ] || [ -z "$VALUE" ]; then
echo "❌ Missing required field: $field"
exit 1
fi
done
ENTRYPOINT=$(cat aerostack.json | jq -r '.entrypoint')
if [ ! -f "$ENTRYPOINT" ]; then
echo "❌ Entrypoint file not found: $ENTRYPOINT"
exit 1
fi
echo "✅ aerostack.json is valid"
echo "Function: $(cat aerostack.json | jq -r '.name') v$(cat aerostack.json | jq -r '.version')"
except:
- main
- tags
# ── Publish draft on push to main ─────────────────────────────────────────────
publish-draft:
stage: publish
image: node:20-alpine
before_script:
- apk add --no-cache curl jq
script:
- |
CONFIG=$(cat aerostack.json)
ENTRYPOINT=$(echo "$CONFIG" | jq -r '.entrypoint // "index.ts"')
CODE=$(cat "$ENTRYPOINT" | jq -Rs .)
# Publish as draft — review on Hub before making public
PAYLOAD=$(jq -n \
--argjson config "$CONFIG" \
--argjson code "$CODE" \
'$config + {code: $code, publish: false}')
RESPONSE=$(curl -s -w "\n%{http_code}" \
-X POST "$AEROSTACK_API" \
-H "X-API-Key: $AEROSTACK_API_KEY" \
-H "Content-Type: application/json" \
-d "$PAYLOAD")
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
echo "$(echo "$RESPONSE" | head -1)" | jq .
[ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ] || exit 1
echo "📝 Published as draft. Go to hub.aerostack.dev/my-functions to review."
only:
- main
# ── Publish live on git tag ────────────────────────────────────────────────────
publish-release:
stage: publish
image: node:20-alpine
before_script:
- apk add --no-cache curl jq
script:
- |
VERSION="${CI_COMMIT_TAG#v}"
jq --arg v "$VERSION" '.version = $v' aerostack.json > tmp.json && mv tmp.json aerostack.json
CONFIG=$(cat aerostack.json)
ENTRYPOINT=$(echo "$CONFIG" | jq -r '.entrypoint // "index.ts"')
CODE=$(cat "$ENTRYPOINT" | jq -Rs .)
PAYLOAD=$(jq -n \
--argjson config "$CONFIG" \
--argjson code "$CODE" \
'$config + {code: $code, publish: true}')
RESPONSE=$(curl -s -w "\n%{http_code}" \
-X POST "$AEROSTACK_API" \
-H "X-API-Key: $AEROSTACK_API_KEY" \
-H "Content-Type: application/json" \
-d "$PAYLOAD")
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
echo "$(echo "$RESPONSE" | head -1)" | jq .
[ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ] || exit 1
echo "🚀 Published $VERSION to Aerostack Hub!"
only:
- tagsThings to Keep in Mind
Protect your CI variable
Always mark AEROSTACK_API_KEY as a masked variable in GitLab. If you also protect it, it will only be available on protected branches and tags — a good default for production publishing.
Version must always be bumped
The Aerostack platform stores a snapshot for every published version. If you try to publish the same version string twice it will still succeed but update the snapshot in place. Consumers who installed the old version will not be automatically updated — version bumping is how you signal an intentional new release.
Draft vs live on push
The publish-draft job above sends publish: false, so a push to main creates/updates the function code as a draft. Only a tagged release (via publish-release) makes it live. This separation is intentional — it lets you review on the Hub UI before going public.
If you prefer push to main = immediately live, change publish: false to publish: true in the publish-draft job.
Multi-function monorepo
If your GitLab repo contains multiple function directories, use the changes keyword to only trigger publishing for modified functions:
publish-rate-limiter:
stage: publish
script:
- cd functions/rate-limiter && <curl command here>
only:
changes:
- functions/rate-limiter/**
refs:
- mainGitLab environments — You can use GitLab’s built-in environment keyword on your publish job to get deployment tracking, rollback history, and approval gates directly in GitLab’s UI. This works independently of Aerostack’s version history.