Skip to main content

Developer Guide

App Bundle Structure

Each app must be bundled as a zip archive (commonly used with the suffix .wave) consisting of:

  • app.toml - required; the platform configuration file
  • static/ - static asset directory, including the app icon (a png file starting with icon) and screenshots (files starting with screenshot)
  • requirements.txt - pip-managed dependencies of the app (can contain references to .whl files included in the .wave using paths relative to the archive root)
  • packages.txt - OS-managed dependencies of the app
  • app source code

You can quickly create a .wave archive by running h2o bundle in your app git repository (see the CLI section)

app.toml

Each app archive has to contain an app.toml configuration file in the TOML format, placed in the root of the .wave archive, example:

[App]
Name = "ai.h2o.wave.my-app"
Version = "0.0.1"
Title = "My awesome app"
Description = "This is my awesome app"
LongDescription = "LongDescription.md"
Tags = ["DATA_SCIENCE"]
InstanceLifecycle = "ON_DEMAND"

[Runtime]
Module = "app.run"
VolumeMount = "/data"
VolumeSize = "1Gi"
ResourceVolumeSize = "2Gi"
MemoryLimit = "500Mi"
MemoryReservation = "400Mi"
CPULimit = "1.5"
CPUReservation = "500m"
GPUCount = 1
GPUType = ""
EnableOIDC = false
EnableSHM = false
RoutingMode = "DOMAIN"

[[Env]]
Name = "ENVIRONMENT_VARIABLE_NAME"
Secret = "SecretName"
SecretKey = "SecretKeyName"

[[Env]]
Name = "ANOTHER_ENVIRONMENT_VARIABLE_NAME"
Value = "some value"

[[File]]
Path = "some/path.file"
Secret = "FileSecretName"
SecretKey = "FileSecretKeyName"

[[File]]
Path = "/another/path.file"
Value = '''
some
string
'''

Required attributes:

  • App
    • Name (string) - a machine-oriented unique identifier of the app (links different app versions together)
    • Version (string) - a https://semver.org version of the app
  • Runtime
    • Module (string) - the name of the main Python module of the app, i.e., the app should be started via python3 -m $module_name (only required if the value of the App.InstanceLifecycle attribute is not LINK; see Link Apps for details)
note

{{App.Name}}-{{App.Version}} must be 63 chars or less and match the regex ^([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$

Optional attributes:

  • App (struct)
    • Title (string) - a human-oriented name of the app for presentation in UI/CLI
    • Description (string) - a single-line description of the app for presentation in UI/CLI
    • LongDescription (string) - a path to a file, relative to the archive root, containing additional multi-line markdown description of the app; although there is no actual restriction of the Markdown format, it is recommended to limit it to bullet-point lists (*), H3 headings (###), and hyperlinks ([]())
    • Tags (list of strings) - tags to automatically assign to the app upon importing, identified by tag name. If the tag is listed as a category tag in the server configuration, the app will be assigned that category upon import.
    • InstanceLifecycle (string) - identifies the instance lifecycle, one of [ON_DEMAND, MANAGED]; defaults to ON_DEMAND when empty.
  • Runtime (struct)
    • RuntimeVersion (string) the name of the runtime version the app will run on top of (similar to a docker base image, see Runtime Environment); empty/unspecified means the platform's default, valid values differ based on platform deployment and configuration.
    • VolumeMount (string) and VolumeSize (string) - request for a volume to persist app instance data across restarts, VolumeMount has to be an absolute path, VolumeSize needs to conform to the k8s resource model.
    • ResourceVolumeSize (string) - request for a volume to persist internal app resources (such as Python venv) across restarts, only recommended for production-quality apps with sizeable resources due to cluster node limits, needs to conform to the k8s resource model.
    • MemoryLimit (string) and MemoryReservation (string) - memory requirements for an instance of the app (defaults to service-wide settings managed by Admins); be conservative with these limits; MemoryLimit is a hard limit on the maximum amount of memory an instance can use before it is OOM-killed; MemoryReservation is how much memory is required to schedule an instance of the app; both need to conform to the k8s resource model.
    • CPULimit (string) and CPUReservation (string) - cpu requirements for an instance of the app (defaults to service-wide settings managed by Admins); be conservative with these limits; CPULimit Maximum CPU usage that an instance of the app can use; CPUReservation How many CPU units is required to schedule an instance of the app; both need to conform to the k8s resource model.
    • GPUCount (int) and GPUType (string) - GPU requirements of the app; empty/unspecified GPUType means the platform's default, valid values differ based on platform deployment and configuration.
    • EnableOIDC (bool) - Enable wave to be setup with OIDC authorization giving access to the user's authentication and authorization information from your app (see Wave Security for details).
    • EnableSHM (bool) - Enable extended docker shared memory for the app; some Libraries like pyTorch use Shared Memory for Multiprocessing. See this issue for more details on this topic.
    • RoutingMode (string) - the routing mode to be used for instances of the app, one of [DOMAIN, BASE_URL]; defaults to DOMAIN when empty. See App Routing Mode for details.
  • Env (struct) - request for a literal value/secret to be injected into an instance at startup-time as an Env variable; repeated; see Utilizing Secrets.
    • Name (string) - name of the Env variable to the injected into the Python process; names prefixed with H2O_CLOUD are disallowed, names prefixed with H2O_WAVE are also disallowed, except H2O_WAVE_APP_MODE and names allowed by the administrator.
    • Secret (string) - name of the shared secret to use; each secret can contain multiple key-value pairs; cannot be used together with Value.
    • SecretKey (string) - name of the key within the secret to use; cannot be used together with Value.
    • Value (string) - the literal value of the Env variable; cannot be used together with Secret/SecretKey.
  • File (struct) - request for a literal value/secret to be injected into an instance at startup-time as a file; repeated; see Utilizing Secrets.
    • Path (string) - path to inject the file to; relative path means relative to the directory with the app code as determined by the platform; path dir cannot be / or . (only subdirs are allowed); path dir has to be unique across all other File configurations; path dir /resources is disallowed.
    • Secret (string) - name of the shared secret to use; each secret can contain multiple key-value pairs; cannot be used together with Value.
    • SecretKey (string) - name of the key within the secret to use; cannot be used together with Value.
    • Value (string) - the literal value of the file; cannot be used together with Secret/SecretKey.
  • Link (struct) - to be filled only if the value of App.InstanceLifecycle is LINK. See Link Apps for details.
    • Address (string) - Full URL of the app link.

Runtime Environment

The platform executes each app in an environment given by its RuntimeVersion. The RuntimeVersion determines the OS, Python version, location of the app code/venv, etc.

Developers can also specify the pip-managed dependencies of the app via standard requirements.txt (can contain references to .whl files included in the .wave using paths relative to the archive root)

Developers can also specify the OS-managed dependencies of the app via packages.txt in a format similar to requirements.txt: one package name per line. These will be installed as given using the package manager given by the RuntimeVersion (e.g., apt-get install).

Developers can further customize the runtime environment by Utilizing Secrets.

The h2o env runtime-version list command will list the runtime-versions available for use.

$ h2o env runtime-version list
NAME STATUS
deb10_py37_wlatest Default
gpu_py37_wlatest
...
note

The platform does not currently provide any provisions for developers to customize the OS and Python versions beyond choosing a specific RuntimeVersion.

We are actively working on improving this.

Standard Environment Variables

When running in an actual App Store deployment, each app instance will be configured with several standard environment variables specifying its context (these might be empty when running locally, e.g., via h2o exec), including:

  • H2O_CLOUD_ENVIRONMENT - typically URL of the App Store deployment
  • H2O_CLOUD_INSTANCE_ID - ID of the app instance
  • H2O_CLOUD_APP_NAME - name of the app
  • H2O_CLOUD_APP_VERSION - version of the app
  • H2O_CLOUD_APP_ID - ID of the app

How-To

Updating App To a Newer Version

The "Catalog" page shows apps with visibility ALL_USERS, so rolling out a new app version is done by:

  1. importing a new version of the app as PRIVATE
  2. testing the new version
  3. changing the visibility of the new version to ALL USERS
  4. (optional) changing the visibility of the old version to PRIVATE

This is based on the Basic Concepts:

Apps are mostly immutable, meaning once uploaded, they cannot be changed (except for visibility). To "update" an app, one has to upload a new version.

and:

Internally, AI App Store treats every app name/version combination as a separate entity. The UI then uses the app name to link several versions together; however each can have different title, description, owner, instances, etc.

An important corollary is that instances of the old app version are not affected by the update (as they are completely separate from the new app version). The update only prevents users from starting new instances of the old version.

Utilizing Secrets

Developers can pass secrets registered with the platform to apps, exposed as environment variables using the [[Env]] section within the app.toml or as files using the [[File]] section. Each specific value from the secret that you want to use in your app needs its own environmental variable or file path.

[[Env]]
Name = "USER_ENV_VAR"
Secret = "private-secret"
SecretKey = "user"

[[File]]
Path = "some/password.file"
Secret = "private-secret"
SecretKey = "password"
import os
@app('/')
async def serve(q: Q):
environment_variable = 'USER_ENV_VAR'
default_value = 'user'
value = os.getenv(environment_variable, default_value)
q.page['my_card'] = ui.form_card(box='1 1 4 4', items=[ui.text(value)])
await q.page.save()

This lets developers link their apps with external dependencies (e.g., S3, Driverless AI) securely, while allowing easy overrides for local development or deployments outside the platform.

The h2o secret list command will let you see a list of secrets available to be used by your apps. The optional -v flag will filter based on PRIVATE and ALL_USERS.

$ h2o secret list
NAME VISIBILITY KEYS
private-secret PRIVATE password, user, host
all-users-secret ALL_USERS accessKey, secretKey
...

App Routing Mode

The app routing mode (Runtime.RoutingMode in app.toml) determines how the app instances' UI is exposed to end users. The currently supported values are

  • DOMAIN - each app instance is exposed on its own subdomain of the Appstore server, i.e., uuid.appstore.domain/app/path.... This is the default setting.
  • BASE_URL - each app instance is exposed on a sub-path of the appstore server, i.e., appstore.domain/instance/uuid/app/path.... This setting requires that the app itself supports serving behind a base URL/path. All apps using Wave SDK 0.20 and later should support this out of the box. The /app/path... portion of the URL path is forwarded to the app container via the H2O_WAVE_BASE_URL environment variable in case it is needed by your application for some reason (in most cases, however, the Wave SDK should handle this for you).

In both cases the app's webserver gets the full request URI as received by the Appstore server.

Redirects: The Appstore server's app router component implements a redirect (via 307 TemporaryRedirect HTTP status) in case an instance is accessed via the wrong URL, i.e. it redirects from sub-path to subdomain for instances with DOMAIN RoutingMode and vice versa for BASE_URL.

App Route

While it is not a strict requirement, since the platform deploys each app with its own Wave server, we advise that apps use / as their main route:

@app('/')
async def serve(q: Q):
pass

Giving an App Instance a Pretty URL

You can leverage App Aliases to give your instances pretty URLs, so that instead of 8184-810243981-23.cloud.h2o.ai your users can access the instance via something like my-pretty-name .cloud.h2o.ai.

Prerequisite: You must be an admin of the environment to run these commands.

To create a single alias for an app for which you want to have a pretty URL, run:

$ h2o admin alias create my-pretty-name <instance id> True

This instance then can be accessed via my-pretty-name.cloud.h2o.ai, accesses to <instance id>.cloud.h2o.ai will result in an HTTP 302 redirect to the alias.

When you’ve created a new app instance, usually because there’s a new version of the app, you may want to change which instance the alias points to. To do this, run:

h2o admin alias assign my-pretty-name <new instance id>`
h2o admin alias promote my-pretty-name

Note that there can be a slight delay before the change gets propagated.

See the CLI documentation for details on these commands.

note

Please note that if the environment requires base URL app routing for all apps, you will need to add this alias to the App Store TLS certificate.

The App Store allows importing apps that do not bundle executable code (and thus cannot have instances) but only represent a link to an external website. This kind of app is referred to as a "Link App". The goal is to inject an external link into the App Store UI/CLI in a way that is consistent in terms of UX with regular apps (app icon, listing on App Store page, categorization, app details with long description and screenshots, etc.).

You can create a link app by setting LINK as the value of App.InstanceLifecycle in app.toml. In such a case, you also need to set the Link.Address value to a URL of your choice. The UI and CLI will then direct users to this URL instead of directing them to an instance of the app.

A link app bundle still contains app.toml and static/, but should not contain any source code or requirements.txt.

A link app can leverage all the parameters in the App section, however the Runtime and File sections must be empty. For example:

[App]
Name = "ai.h2o.examples.link"
Version = "0.0.1"
Title = "Example: Link"
Description = "Showcasing the link functionality."
InstanceLifecycle = "LINK"

[Link]
Address = "https://h2o.ai"

App Configuration with the User Interface

While tags and visibility for an App can be configured via the CLI, these attributes can also be set using the user interface, as described below:

note

Since the user interface is being continually improved, it is possible that the images below will not match exactly what you see in your version of H2O AI Cloud.

On the "My Apps" page, click on the pencil icon for the app you wish to edit:

My Apps - edit icon

This will bring up a panel on the right side of the user interface which will allow you to edit the values for:

  • Visibility (See Visibility for more information)
  • Categories
  • Badges
  • Authorization Tags
App configuration panel

Tags will show up in the "Categories", "Badges", or "Authorization Tags" select menus based on the following criteria:

  • If the tag has isCategory set to true it will be treated as a "Category", which allows it to be filtered on the App Store's main page.
  • If the tag has one or more visitorRoles set, it will be treated as an "Authorization Tag".
  • Otherwise, the tag will serve as a "Badge" in the App Store UI: Example badge

See App Tag to learn more about tags.