Application Licenses

Some applications on JuliaHub run third-party software that needs its own license — NONMEM, Pirana and Monolix are the common examples in the pharmacometrics images. Application Licenses lets an administrator upload such a license once and have the platform deliver it into the right users' jobs and interactive sessions, without rebuilding a container image for every customer or every license renewal.

The same panel also configures Application Integrations, which use the identical delivery mechanism to pass a piece of non-secret configuration (such as a Posit Package Manager URL) into an app.

This page describes what the platform actually does. It is worth reading the How delivery works section before uploading anything, because two details — the exact spelling of the program field and the requirement to grant the license twice — decide whether a license reaches a job at all, and both fail quietly when they are wrong.

This is not the license for JuliaHub itself

Your JuliaHub installation has its own license (a Replicated license ID or license.yaml) that controls which platform features you are entitled to. That is a separate thing, set up during installation and described in Common Prerequisites. Nothing on this page affects it.

Opening the Panel

  1. Open the Admin section in the left sidebar.
  2. Select Configure Licenses.

The panel is available to users with the platform administrator or customer administrator role. It shows two tables:

  • Application Licenses — license files for third-party programs.
  • Application Integrations — configuration values delivered by the same mechanism, for example a Posit Package Manager URL.

Both tables list the Name, the Program the entry is for, and when it was last Updated, with Edit and Delete buttons on each row.

How delivery works

Understanding the delivery path explains every constraint below.

When a user launches a job or an interactive application, the platform looks up the licenses that apply to that combination of user and application, and writes them into a Kubernetes Secret that is mounted into the container. Each license becomes a file at:

/var/run/secrets/jobsecrets/juliahub-license-<program>

where <program> is the value you typed into the What is this license for? field, reproduced verbatim. The image's own startup script then picks that file up and puts it wherever the third-party program expects it. For NONMEM, a startup hook in the image copies the file to /opt/NONMEM/nm_current/license/nonmem.lic.

Three consequences follow, and they are the things that actually go wrong:

  • The program name is a filename, and images match it exactly. The image looks for one specific string, including its capitalisation. For NONMEM images the string is Nonmem — not NONMEM, not nonmem. Get it wrong and the file is still created, just under a name nothing reads: the license is silently absent and NONMEM fails to run as though no license had ever been uploaded. See Program names images look for.
  • The license is assembled when the job starts. Uploading or changing a license has no effect on sessions that are already running. Users must restart their job or interactive session to pick up a new or rotated license.
  • Access is an intersection, not a union. A license reaches a container only if it is granted both to the application and to the launching user. See Granting access.

Older job images built against JobAPI v0.1.0 receive the same files under /var/run/secrets/<job name>secret/ instead. Images that support both look in both locations.

This panel does not license Windows Workstations

Everything on this page applies to Linux batch jobs and interactive applications. Windows Workstations take a different path: their third-party licenses are baked into the workstation image when JuliaHub builds it for your site, so a license added here will not reach NONMEM, Phoenix, Pirana or Monolix running on a Windows Workstation. Licensing those is a request to JuliaHub support, which results in a new workstation image. See Workstation Customization for what you can change yourself.

Adding a license

Click Add above the Application Licenses table and fill in the form.

Name — a label for your own reference, shown in the table. It is limited to 15 characters in the form and cannot be changed after the entry is created, so pick something you will recognise, such as nonmem-2026.

License file — browse to the license file on your machine. The file must be under 5 MB. Its contents are stored by the platform and delivered verbatim; the platform does not parse, validate or interpret them in any way.

Description — optional free text, up to 360 characters. Use it for the things the platform does not track, such as the expiry date and who to contact for a renewal.

What is this license for? — the program name. This is the critical field: it becomes part of the delivered filename, and it must match what the image expects, character for character. It may contain letters, digits, hyphens and underscores, must start with a letter or digit, and is limited to 32 characters. Spaces and dots are rejected.

What cloud apps should this license be applied to — a comma-separated list of the applications that should receive this license. These are the platform's internal product names, not the display names users see, and they are validated: an unrecognised name is rejected with a message naming the entries that failed. See Finding the right application name.

Apply to all users — tick this to make the license available to everyone on the platform. Ticking it disables the two fields below.

Enter the groups that should have access to this license — a comma-separated list of identity provider groups.

Enter the user IDs that should have access to this license — a comma-separated list of individual users.

Click Save. If Apply to all users is not ticked, you must fill in at least one group or one user; an entry scoped to no one is rejected.

Granting access

Access is the intersection of two separate grants. The license is delivered only when both are true:

  1. The license is granted to the application the user is launching (the "cloud apps" field), and
  2. The license is granted to the launching user — through Apply to all users, through their user ID, or through one of their identity provider groups.

Listing the application on its own delivers nothing, because the intersection with the user side is empty. This is the most common reason a correctly-uploaded license never appears in a job. For a site-wide rollout, list the applications and tick Apply to all users.

One license per program, per application

For a given application, you can have only one active license of a given program name in a given scope. The platform checks this when you save and refuses the entry with a message such as A Nonmem license for all users for app nonmem-batch already exists.

This is a real constraint rather than a formality: if two entries of the same program name could both match one user, only one of them would be delivered. If a customer needs several pieces of secret material for one application, put them in a single file — for example one JSON document holding several values — rather than splitting them across entries.

Different program names coexist without any problem. A user can receive a Nonmem license and a Pirana license for the same application.

Program names images look for

The program name must match what the image's startup script looks for. These are the values in current images:

ProgramValue for What is this license for?Where the image puts it
NONMEMNonmem/opt/NONMEM/nm_current/license/nonmem.lic
Posit Package Manager (an integration, not a license)POSIT_PACKAGE_MANAGERRead into the POSIT_PACKAGE_MANAGER environment variable and written into the R configuration

For any other program, confirm the expected string with JuliaHub support before uploading. There is no list in the UI, no drop-down and no validation against known programs: the field is free text, and an unrecognised value produces a file that nothing reads rather than an error.

The capitalisation of `Nonmem` is not a typo

NONMEM images look for juliahub-license-Nonmem. Entering NONMEM creates juliahub-license-NONMEM, which no image reads. Nothing in the UI, the logs or the job output will tell you this happened — the job simply behaves as if unlicensed.

Finding the right application name

The "cloud apps" field takes the platform's product names. Examples of names in a typical pharmacometrics install:

  • nonmem-batch — NONMEM Batch
  • rstudio — RStudio
  • pumaside — Pumas IDE
  • pumas-batch, pumas-interactive — Pumas

The set of products differs between installations, and only the products configured on your platform are accepted. If you are unsure of a name, enter your best guess and save: the error message lists back exactly the entries that were not recognised, which is the quickest way to check a name.

Editing a license

Click Edit on a row to upload a replacement file, change the description or program, or change who the license applies to. The Name is fixed and shown greyed out.

Uploading a replacement file is how you handle a renewal. As with a new upload, running jobs and sessions keep the old license until they are restarted.

If you clear the groups and users without ticking Apply to all users, the update is rejected — an entry must always be scoped to someone.

Deleting a license

Click Delete on a row and confirm. The entry stops being delivered to jobs that start afterwards; sessions already running are unaffected until they restart.

Deletion is a soft delete. The row disappears from the panel, but the stored license and its access records are retained in the platform database and marked deleted rather than being erased. If you need a license to be genuinely removed from storage — for example because a vendor requires it — raise it with JuliaHub support; it is not something the panel can do.

Application Integrations

The second table on the panel works identically, but is intended for configuration rather than secrets. The worked example is Posit Package Manager: an entry with the program name POSIT_PACKAGE_MANAGER whose "file" contains a URL, granted to the rstudio application. On startup, RStudio images read that file, export it as the POSIT_PACKAGE_MANAGER environment variable, and point R's package repositories at it.

Everything in this page about program names, grants and restarts applies to integrations too.

What the platform does not do

State these plainly when planning a rollout, because each is a manual process:

  • No expiry tracking. The platform stores the license as opaque data and never reads it. There is no expiry column, no warning before a license lapses and no renewal reminder. The first sign of an expired license is usually a failing job. Record expiry dates in the Description field and track renewals outside JuliaHub.
  • No seat or concurrency accounting. The platform delivers the file to everyone in scope. Any seat limit or concurrency rule in your agreement with the vendor is yours to enforce; JuliaHub neither counts nor caps usage.
  • No audit events. Adding, editing and deleting licenses does not generate entries in Audit Events. The platform records who last changed each entry and when, and the panel shows the Updated timestamp, but there is no exportable history of license changes.
  • No validation of license contents. A truncated, corrupt or wrong-product license file uploads successfully and is delivered successfully; it only fails when the third-party program tries to use it.
  • No network license server management. Some products (Monolix under RLM, Posit Workbench in floating mode) check a license out from a vendor-run license server at run time instead of reading a file. For those, what matters is that the job's network can reach the vendor's host, which is a networking question, not something this panel configures. Raise these with JuliaHub support.

Security properties

Worth knowing before a security review asks:

  • The license file is mounted into the container world-readable, on a volume that is not marked read-only. Containers are per-user, so the exposure is limited to that user's own session, but any process running in their container can read the file.
  • Licenses are stored in the platform database, encoded but not encrypted beyond whatever encryption at rest your database is configured with. Treat the panel as a store for vendor licenses, not as a general-purpose secrets manager.
  • Licenses are delivered only into sessions matching both grants, and are never written into a container image, so rotating a license does not require rebuilding or redistributing an image.

Troubleshooting

The program says it is unlicensed, and the license is definitely uploaded. Work through these in order — the first two account for most cases:

  1. Check the program name's capitalisation. For NONMEM it must be exactly Nonmem. See Program names images look for.
  2. Check both grants. The user must be covered by Apply to all users, their user ID, or one of their groups — and the application they launched must be listed in the cloud apps field. Open the entry with Edit to see both.
  3. Restart the session. A license uploaded after the job started is not picked up.
  4. Confirm the application name. A license granted to pumas-batch does nothing for a user launching nonmem-batch.

If it still fails, ask the user to open a terminal in a running session and list /var/run/secrets/jobsecrets/. If a file named juliahub-license-<program> is there, the platform side is working and the problem is in the image or the license file itself; if it is absent, the problem is the program name or the grants.

Saving returns "already exists". An active entry with the same program name already covers one of the applications you listed, in an overlapping scope. Edit that entry instead of adding a second one, or narrow the scope of one of them.

Saving returns "not valid product names". One or more entries in the cloud apps field are not products on this platform. The message names the failing entries; correct them and save again.

A license needs to reach an image that does not read it. Delivery only puts a file in a known location — the image must have a startup script that looks for it. Images JuliaHub builds for licensed products already do. A custom image built in-house needs that step added; see Building your own JuliaHub Job Images.