Skip to main content
We only support custom runtimes for Python and JavaScript/TypeScript.
Custom runtimes allow you to install dependencies for use in your Python and JavaScript code. After building a custom runtime, you can use it with the Execute Code endpoint. You can create custom runtimes from the Riza Dashboard, but in this guide, we’ll use the Custom Runtimes API to programmatically create and manage custom runtimes.

Set up

Before getting started, you’ll need an API key from the Riza Dashboard. Export it as an environment variable within your shell:
export RIZA_API_KEY=<your-api-key>

Create a Python custom runtime

In this example, we’ll create a custom Riza runtime environment with the Markdown Python package, which converts Markdown to HTML. The required parameters are “name”, “language” and “manifest_file”, which is a simple PyPi requirements file declaring your dependencies.
import Riza from "@riza-io/api";

const riza = new Riza();

async function main() {
  const resp = await riza.runtimes.create({
    name: "acme_corp_custom_runtime",
    language: "python",
    manifest_file: {
      name: "requirements.txt",
      contents: "markdown==3.7",
    },
  });
  console.log(resp);
}

main();
from rizaio import Riza

riza = Riza()

resp = riza.runtimes.create(
    name="acme_corp_custom_runtime",
    language="python",
    manifest_file={
        "name": "requirements.txt",
        "contents": "markdown==3.7",
    },
)

print(dict(resp))
package main

import (
	"context"
	"log"
	"log/slog"

	"github.com/riza-io/riza-api-go"
)

func main() {
	client := riza.NewClient()
	params := riza.RuntimesCreateParams{
		Name: riza.F("acme_corp_custom_runtime"),
		Language: riza.F(riza.RuntimeNewParamsLanguagePython),
		ManifestFile: &riza.ManifestFile{
			Name:    riza.F("requirements.txt"),
			Contents: riza.F("markdown==3.7"),
		},
	}
	resp, err := client.Runtimes.Create(context.Background(), params)
	if err != nil {
		log.Fatal(err)
	}
	slog.Info(
		"created custom runtime",
		"id", resp.ID,
		"revisionId", resp.RevisionID,
		"status", resp.Status
	)
}
The resulting runtime object you receive from the API will look like this. Take note of the id and revision_id parameters, which we’ll use in future steps.
{
	'id': '01JFESTGXM...',
	'language': 'python',
	'name': 'acme_corp_custom_runtime',
	'revision_id': '01JFETP2VD...',
	'status': 'pending',
	'additional_python_imports': '',
	'manifest_file': ManifestFile(contents='markdown==3.7', name='requirements.txt')
}
Each custom runtime will have one or more associated revisions. The first revision is automatically created when you create a runtime. You’ll have a new revision_id each time you update your dependencies, while the runtime id will remain the same.

Create a JavaScript custom runtime

In this example, we’ll create a custom Riza JavaScript runtime environment with the marked package.
import Riza from "@riza-io/api";

const riza = new Riza();

async function main() {
  const resp = await riza.runtimes.create({
    name: "acme_corp_custom_runtime",
    language: "javascript",
    manifest_file: {
      name: "package.json",
      contents: '{"dependencies": {"marked": "^15.0.6"}}',
    },
  });
  console.log(resp);
}

main();
from rizaio import Riza

riza = Riza()

resp = riza.runtimes.create(
    name="acme_corp_custom_runtime",
    language="javascript",
    manifest_file={
        "name": "package.json",
        "contents": '{"dependencies": {"marked": "^15.0.6"}}',
    },
)

print(dict(resp))
package main

import (
	"context"
	"log"
	"log/slog"

	"github.com/riza-io/riza-api-go"
)

func main() {
	client := riza.NewClient()
	params := riza.RuntimesCreateParams{
		Name: riza.F("acme_corp_custom_runtime"),
		Language: riza.F(riza.RuntimeNewParamsLanguageJavascript),
		ManifestFile: &riza.ManifestFile{
			Name:    riza.F("package.json"),
			Contents: riza.F('{"dependencies": {"marked": "^15.0.6"}}'),
		},
	}
	resp, err := client.Runtimes.Create(context.Background(), params)
	if err != nil {
		log.Fatal(err)
	}
	slog.Info(
		"created custom runtime",
		"id", resp.ID,
		"revisionId", resp.RevisionID,
		"status", resp.Status
	)
}

Wait for the custom runtime to build

Building a custom runtime is not instant. It will likely take a few seconds, and could take more than a minute. To check a build’s status, use the Riza API to fetch the custom runtime. The runtime object’s status parameter indicates whether the latest build is complete and whether it succeeded or failed. To monitor progress, periodically call the API until the status is either "succeeded" or "failed". The code below demonstrates this approach.
import Riza from "@riza-io/api";

const riza = new Riza();

async function pollRuntimeStatus(runtimeId) {
  const pollingIntervalSeconds = 2000;

  while (true) {
    const resp = await riza.runtimes.get(runtimeId);
    console.log(resp.status);

    if (resp.status === "succeeded" || resp.status === "failed") {
      return resp.status;
    }

    await new Promise((resolve) => setTimeout(resolve, pollingIntervalSeconds));
  }
}

const finalStatus = await pollRuntimeStatus("01JFESTGXM..."); // runtime `id` from previous step
console.log(`Final status: ${finalStatus}`);
import asyncio
from rizaio import AsyncRiza

riza = AsyncRiza()

async def poll_runtime_status(runtime_id):
    polling_interval_seconds = 2

    while True:
        resp = await riza.runtimes.get(id=runtime_id)
        status = dict(resp)["status"]
        print(status)

        if status in ["succeeded", "failed"]:
            return status

        await asyncio.sleep(polling_interval_seconds)

# Usage
final_status = asyncio.run(poll_runtime_status("01JFESTGXM...")) # runtime `id` from previous step
print(f"Final status: {final_status}")
package main

import (
	"context"
	"log/slog"
	"time"

	"github.com/riza-io/riza-api-go"
)

func pollRuntimeStatus(client *riza.Client, runtimeID string) (string, error) {
	pollingInterval := 2 * time.Second
	ticker := time.NewTicker(pollingInterval)
	defer ticker.Stop()

	for {
		<-ticker.C
		resp, err := client.Runtimes.Get(context.Background(), runtimeID)
		if err != nil {
			return "", err
		}

		slog.Info("Polling runtime status", "status", resp.Status)

		switch resp.Status {
		case "succeeded", "failed":
			return resp.Status, nil
		}
	}
}

func main() {
	client := riza.NewClient()
	runtimeID := "01JFESTGXM..." // runtime `id` from previous step

	finalStatus, err := pollRuntimeStatus(client, runtimeID)
	if err != nil {
		slog.Error("Error polling runtime status", "error", err)
		return
	}
	slog.Info("Final status", "final_status", finalStatus)
}

Use the custom runtime

If the custom runtime build succeeds, you can now use it to run code that imports the Markdown package. You’ll need the runtime revision ID from the first step to use the runtime with the Execute Code endpoint. Now, you can execute Python code with the markdown package using the custom runtime from the previous step.
import Riza from "@riza-io/api";

const pythonCode = `
import sys
import markdown

print(markdown.markdown(sys.stdin.read()))
`;

const riza = new Riza();

async function main() {
  const resp = await riza.command.exec({
    runtime_revision_id: "01JFETP2VD...",
    code: pythonCode,
    stdin: "**Hello, Markdown!**",
  });
  console.log(resp);
}

main();
from rizaio import Riza

CODE = """
import sys
import markdown

print(markdown.markdown(sys.stdin.read()))
"""


client = Riza()

resp = client.command.exec(
    runtime_revision_id="01JFETP2VD...",
    code=CODE,
    stdin="**Hello, Markdown!**",
)

print(dict(resp))
package main

import (
	"context"
	"log"
	"log/slog"

	"github.com/riza-io/riza-api-go"
)

const pythonCode = `
import sys
import markdown

print(markdown.markdown(sys.stdin.read()))
`

func main() {
	client := riza.NewClient()
	params := riza.CommandExecParams{
		RuntimeRevisionId: riza.F("01JFETP2VD..."),
		Code:    riza.F(pythonCode),
		Stdin:   riza.F("**Hello, Markdown!**"),
	}
	resp, err := client.Command.Exec(context.Background(), params)
	if err != nil {
		log.Fatal(err)
	}
	slog.Info(
		resp.Stdout,
		"code", resp.ExitCode,
		"stderr", resp.Stderr,
	)
}
Similarly, you can execute JavaScript code with the marked package using the custom runtime from the previous step.
import Riza from "@riza-io/api";

const jsCode = `
import { marked } from 'marked'

const input = await new Response(process.stdin).text();

console.log(marked(input));
`;

const riza = new Riza();

async function main() {
  const resp = await riza.command.exec({
    runtime_revision_id: "01JFETP2VD...",
    code: jsCode,
    stdin: "**Hello, Markdown!**",
  });
  console.log(resp);
}

main();
from rizaio import Riza

CODE = """
import { marked } from 'marked'

const input = await new Response(process.stdin).text();

console.log(marked(input));
"""


client = Riza()

resp = client.command.exec(
    runtime_revision_id="01JFETP2VD...",
    code=CODE,
    stdin="**Hello, Markdown!**",
)

print(dict(resp))
package main

import (
	"context"
	"log"
	"log/slog"

	"github.com/riza-io/riza-api-go"
)

const jsCode = `
import { marked } from 'marked'

const input = await new Response(process.stdin).text();

console.log(marked(input));
`

func main() {
	client := riza.NewClient()
	params := riza.CommandExecParams{
		RuntimeRevisionId: riza.F("01JFETP2VD..."),
		Code:    riza.F(jsCode),
		Stdin:   riza.F("**Hello, Markdown!**"),
	}
	resp, err := client.Command.Exec(context.Background(), params)
	if err != nil {
		log.Fatal(err)
	}
	slog.Info(
		resp.Stdout,
		"code", resp.ExitCode,
		"stderr", resp.Stderr,
	)
}

Next steps

Check out the Create Runtime Revision endpoint if you need to add or remove dependencies. If you have questions, please drop us a line in Discord.