Publish a Compute-to-Data Algorithm
You already have the algorithm written and tested locally. This guide covers the other 50%: building the image with buildx, uploading it to a registry, fixing its checksum, and publishing it on the Pontus-X portal with pontus-x_cli —including the link with the dataset that will be able to execute it—.
This guide assumes you have already built and tested your algorithm. If you haven't yet, start with Build a Compute-to-Data Algorithm.
This same content is available as a slide presentation (navigable with the arrows ← →).
From source code to an executable asset
Publishing a C2D algorithm is a chain of steps where each one feeds the next. The critical link is the checksum: it connects the physical image with its on-chain description.
The portal does not store your code: it stores a reference to the image + its checksum. The C2D node downloads that exact image and executes it in isolation on the dataset.
What you need to have on hand
- 🐳 Docker + buildx — Recent Docker (buildx is included). Check with
docker buildx version. - 📦 A registry account — your own registry (
registry.agrospai.udl.cat) or Docker Hub, withpushpermissions. - 🟢 Node.js + npm — to install and run
pontus-x_cliand build scripts with Nautilus. - 🔑 A wallet — a
.jsonkey file (exportable withexport-private-key) to sign the on-chain publication. - 🧩 The algorithm repo — with its
Dockerfilealready tested locally (see the build guide). - 💡 Your configs repo — your own repo where you store your publication scripts. AgrospAI uses a private one (
agrospai-configs); create yours with the same structure.
Once: npm install -g pontus-x_cli —or use it from the configs repo, which already lists it as a dependency—.
Why image architecture matters
The algorithm does not run on your laptop, but on the C2D provider's compute node. If the image architecture does not match that of the node, the job fails to start (exec format error).
- Most C2D nodes are
linux/amd64— the bare minimum. - If you build from an Apple Silicon Mac, your native build is
arm64: it won't work by default. - A multi-architecture image bundles several, and the node chooses its own.
If you don't know where it will run, build multi-arch (linux/amd64,linux/arm64). If you do know, just target that platform. From an ARM Mac, buildx can build amd64 via emulation (QEMU) —slower, but without needing another machine—.
Step 1 · Build and upload with docker buildx
Unlike docker build, buildx builds and uploads in a single step with --push, and supports multiple platforms at once with --platform.
- Create a builder once:
docker buildx create --use. --platformdefines the architecture(s).--pushuploads the result directly to the registry.- The tag rules:
registry/namespace/name:tag.
docker buildx create --name agrospai --use
docker buildx inspect --bootstrap
- Option A · single architecture
- Option B · multi-architecture
# The node architecture (almost always amd64)
docker buildx build \
--platform linux/amd64 \
-t registry.agrospai.udl.cat/library/clip-raster:latest \
--push .
Faster and lighter build. Ideal when you know the target node.
# A single tag for amd64 and arm64 nodes
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t registry.agrospai.udl.cat/library/clip-raster:latest \
--push .
The registry stores a manifest index that points to each variant; the node chooses its own.
The final . is the build context (the root of the repo, where the Dockerfile is located).
A multi-platform image cannot be loaded into local Docker (--load); it must go to the registry, which is responsible for storing the manifest index.
Step 2 · Where the image lives: your own registry or Docker Hub
The C2D node must be able to download your image. This means the registry must be accessible (public, or with credentials the node knows).
- Your own registry —
registry.agrospai.udl.cat/library/<name>. Total control. - Docker Hub —
docker.io/<user>/<name>. The namespace is your user. - Authenticate with
docker loginbefore doing--push.
# Your own registry
docker login registry.agrospai.udl.cat
# Username: ...
# Password: ...
# Docker Hub
docker login # docker.io by default
# After login, the same build with --push already uploads.
# Naming according to destination:
# registry.agrospai.udl.cat/library/clip-raster:latest
# docker.io/mycompany/clip-raster:latest
The image in the publication script must be written exactly the same as the tag with which you uploaded: same host, same namespace, same name. Use a versioned tag (:0.1, :2025-06) in addition to :latest: it allows you to re-publish without overwriting previous versions.
Step 3 · The checksum: fixing the exact image
C2D does not trust the tag (:latest can change). It fixes the image by its sha256 digest —an immutable hash of the content—. That digest is the checksum in the publication script.
- The
--pushalready prints the digest when finished. - Or retrieve it with
buildx imagetools inspect. - For multi-arch, use the digest of the manifest index (the top-level one).
# Get the digest of the already uploaded image
docker buildx imagetools inspect \
registry.agrospai.udl.cat/library/clip-raster:latest
# Output (summarized):
# Name: registry.agrospai.udl.cat/library/clip-raster:latest
# MediaType: application/vnd.oci.image.index.v1+json
# Digest: sha256:b6ba37cc7a77c936bc03a58b0bbaf9debb55b2
# 5f4b04a097181ee442ea5e50f2
# Manifests:
# linux/amd64 sha256:...
# linux/arm64 sha256:...
Copy the top-level Digest (the one from the index). That's what goes, as is with its sha256: prefix, into the checksum field.
Every time you rebuild and upload, recalculate the checksum and update the asset (re-publish or edit-algo). Otherwise, the node executes the old image.
Step 4 · Anatomy of a publication script
Each asset is a folder inside scripts/ with two files. pontus-x_cli publish receives the path to index.ts, imports it, and executes its publish function.
index.ts— builds the asset with Nautilus.description.md— the text users will see on the portal.- The signature is fixed:
publish(folder, connection, provider, dryRun).
your-repo-configs/ # private, yours (same structure)
└── scripts/
└── clip-raster/
├── index.ts # describes and publishes the asset
└── description.md # public sheet (Markdown)
import { readFileSync } from 'fs';
import {
AssetBuilder, ConsumerParameterBuilder,
FileTypes, ServiceTypes, ServiceBuilder
} from '@deltadao/nautilus'
import { ServiceFileType } from '@deltadao/nautilus'
const publish = async (
folder: string, connection: any,
provider: string, dryRun: boolean
) => {
// 1) metadata 2) algoMetadata 3) service 4) publish
}
AgrospAI maintains its scripts in a private repo (agrospai-configs). You create yours by replicating this same structure —the repo name doesn't matter; what matters is scripts/<asset>/index.ts—.
(1) Describe the asset with AssetBuilder
The AssetBuilder declares what the asset is: type, name, authorship, description, tags, and the NFT that represents it on-chain.
setType('algorithm')— distinguishes algorithms from datasets.- The description is read from
description.md. - The tags improve discovery on the portal.
setOwneruses the connected wallet's address.
const assetBuilder = new AssetBuilder();
assetBuilder.setType('algorithm')
.setName('Clip Raster')
.setAuthor('Your Organization')
.setOwner(connection.wallet.address)
.setDescription(
readFileSync(`${folder}/description.md`, 'utf8'))
.addTags(['agrospai', 'remote-sensing',
'sentinel', 'raster'])
.setLicense('MIT')
.setNftData({
name: 'Clip Raster',
symbol: 'AgrospAI-ClipRaster',
templateIndex: 1,
tokenURI: 'https://.../logo.png',
transferable: false
});
setNftData defines the non-fungible token that gives identity and ownership to the asset. transferable: false anchors it to your organization.
(2) Here goes the checksum: setAlgorithm
The algoMetadata block is the bridge between the portal and your image: it declares which image to execute, with which entrypoint, and fixed to which checksum.
image+tag— exactly what you uploaded to the registry.checksum— thesha256digest from step 3.entrypoint—ocean-execute --base-dir /data.consumerParameters— the parameters the user fills in when launching the job.
const cpb = new ConsumerParameterBuilder();
const refcat = cpb
.setType('text').setName('refcat')
.setLabel('Cadastral Reference')
.setDescription('Cadastral reference code')
.setRequired(true)
.setDefault('25168A010000060000IZ')
.build();
const algoMetadata = {
language: 'python',
version: '0.1',
container: {
entrypoint: 'ocean-execute --base-dir /data',
image: 'registry.agrospai.udl.cat/library/clip-raster',
tag: 'latest',
checksum: 'sha256:b6ba37cc7a77c936bc03a58b0bb...'
},
consumerParameters: [ refcat ]
};
assetBuilder.setAlgorithm(algoMetadata);
image, tag, and checksum must correspond to the same published image. A mismatch here is the #1 cause of jobs that fail to start.
(3) The compute service, the price, and the datatoken
The ServiceBuilder defines how the asset is consumed. For a C2D algorithm, it is a COMPUTE type service, with a backup file and a pricing policy.
serviceEndpoint— theproviderthat controls the asset.addFile— the URL of the algorithm resource... or a URL to a MinIO / object storage with protected variables (username, password, tokens).setTimeout(0)— access without expiry after purchase.setPricing— free or fixed rate (EUROe).
const sb = new ServiceBuilder({
serviceType: ServiceTypes.COMPUTE,
fileType: FileTypes.URL });
const urlFile: ServiceFileType<FileTypes> = {
type: 'url',
// algorithm resource... or a private bucket with
// auth secrets (mounted in /data/transformations)
url: 'https://minio.agrospai.udl.cat/secrets/<id>/algorithm',
method: 'GET' };
const service = sb
.setServiceEndpoint(provider)
.setTimeout(0)
.addFile(urlFile)
.setPricing(connection.pricingConfig.fixedRateEUROe(1))
.setDatatokenNameAndSymbol(
'AgrospAI Clip Raster', 'AgrospAI-ClipRaster')
.build();
assetBuilder.addService(service);
const asset = assetBuilder.build();
if (!dryRun) await connection.nautilus.publish(asset);
The file can point to a private bucket (minio.agrospai.udl.cat/secrets/…) whose content the node mounts in /data/transformations. This way the algo reads credentials —e.g., the OData login in clip-raster— without embedding them in the image.
Step 5 · Login and publish with pontus-x_cli
Configure the network in .env, authenticate with your key file, and publish by pointing to the --provider and the script folder.
.env— sets theNETWORK(e.g.,PONTUSXDEV).login— loads your private key from your.json(your own account/wallet).--dry-runfirst; then without it to publish.- Note the DID it returns: you'll need it for the link.
NETWORK=PONTUSXDEV
# PRIVATE_KEY is filled by `login`
# 1) Authenticate with YOUR key file (your account)
pontus-x_cli login YOUR-KEY.json
# 2) Dry run: prints the asset without publishing
pontus-x_cli publish \
--provider https://provider.agrospai.udl.cat \
scripts/clip-raster/index.ts --dry-run
# 3) Publish for real → returns the DID
pontus-x_cli publish \
--provider https://provider.agrospai.udl.cat \
scripts/clip-raster/index.ts
The example .json is UdL's; you use yours (your wallet, exportable with export-private-key). The login determines who signs and who appears as the owner —everything else is identical—. Where does the .json come from? From pontus-x_cli export-private-key, which dumps an existing wallet to a file (it doesn't create one). Keep it safe: whoever has it can sign as you.
Step 6 · The link with the dataset
By design, a C2D computation is always launched against at least one dataset, and that dataset must explicitly authorize the algorithm with edit-trusted-algos.
- If your algo consumes data: link it to the real dataset.
- If it does not consume data (it obtains it from an external API): link it to the Empty Placeholder Dataset.
edit-trusted-algosoverwrites the list: pass all allowed algos at once.
# Authorize your algo on the dataset (as dataset owner)
pontus-x_cli edit-trusted-algos \
did:op:<DATASET> --algos did:op:<ALGORITHM>
# 1) Publish the empty dataset once
pontus-x_cli publish \
--provider https://provider.agrospai.udl.cat \
scripts/placeholder-dataset/index.ts
# 2) Authorize your algo on the placeholder
pontus-x_cli edit-trusted-algos \
did:op:<PLACEHOLDER> --algos did:op:<ALGORITHM>
The algorithm ignores the content of the placeholder and produces its output from its own source. You launch the job with placeholder + algorithm.
edit-trusted-algos is executed by the dataset owner. login with that identity beforehand (not the algorithm's, if they are different).
In summary
- Create the builder:
docker buildx create --use. - Build with
--platform(multi-arch if in doubt) and--push. - Upload to a registry accessible by the C2D node.
- Get the
sha256withimagetools inspect. - Create the script folder:
index.ts+description.md. - Put consistent image · tag · checksum in
setAlgorithm. loginwith the owner identity.publish --dry-run, review, and publish for real.- Note the algorithm's DID.
edit-trusted-algoson the dataset (or the placeholder).
clip-raster reference code: github.com/AgrospAI/ocean-algo. Publication scripts: create your own repo with the agrospai-configs structure (private). CLI: github.com/AgrospAI/pontus-x_cli.
Doubts about the build, the registry, or the publication? The AgrospAI team will assist you with the integration.