Skip to main content

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—.

Before you start

This guide assumes you have already built and tested your algorithm. If you haven't yet, start with Build a Compute-to-Data Algorithm.

Prefer presentation format?

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.

Key idea

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, with push permissions.
  • 🟢 Node.js + npm — to install and run pontus-x_cli and build scripts with Nautilus.
  • 🔑 A wallet — a .json key file (exportable with export-private-key) to sign the on-chain publication.
  • 🧩 The algorithm repo — with its Dockerfile already 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.
Install the CLI

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.
Practical rule

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.
  • --platform defines the architecture(s).
  • --push uploads the result directly to the registry.
  • The tag rules: registry/namespace/name:tag.
terminal — create the builder (once)
docker buildx create --name agrospai --use
docker buildx inspect --bootstrap
terminal
# 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.

The final . is the build context (the root of the repo, where the Dockerfile is located).

Multi-arch needs --push

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 registryregistry.agrospai.udl.cat/library/<name>. Total control.
  • Docker Hubdocker.io/<user>/<name>. The namespace is your user.
  • Authenticate with docker login before doing --push.
terminal
# 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
Important

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 --push already 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).
terminal
# 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.

If the image changes, the digest changes

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/
your-repo-configs/         # private, yours (same structure)
└── scripts/
└── clip-raster/
├── index.ts # describes and publishes the asset
└── description.md # public sheet (Markdown)
scripts/clip-raster/index.ts
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
}
Create your own repo

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.
  • setOwner uses the connected wallet's address.
index.ts · (1) metadata
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
});
The asset NFT

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 — the sha256 digest from step 3.
  • entrypointocean-execute --base-dir /data.
  • consumerParameters — the parameters the user fills in when launching the job.
index.ts · (2) algoMetadata + params
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);
The three must match

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 — the provider that 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).
index.ts · (3) service + (4) publish
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);
Auth secrets outside the code

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 the NETWORK (e.g., PONTUSXDEV).
  • login — loads your private key from your .json (your own account/wallet).
  • --dry-run first; then without it to publish.
  • Note the DID it returns: you'll need it for the link.
.env
NETWORK=PONTUSXDEV
# PRIVATE_KEY is filled by `login`
terminal
# 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
Your own account

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.

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-algos overwrites the list: pass all allowed algos at once.
terminal · case with own dataset
# Authorize your algo on the dataset (as dataset owner)
pontus-x_cli edit-trusted-algos \
did:op:<DATASET> --algos did:op:<ALGORITHM>
terminal · case without data (placeholder)
# 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.

Who authorizes

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 sha256 with imagetools inspect.
  • Create the script folder: index.ts + description.md.
  • Put consistent image · tag · checksum in setAlgorithm.
  • login with the owner identity.
  • publish --dry-run, review, and publish for real.
  • Note the algorithm's DID.
  • edit-trusted-algos on the dataset (or the placeholder).
Resources

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.