Perl integration
This document explains how key hosting API calls map to on-disk directory layout, environment variable configuration, and runtime behavior.
Installation
Section titled “Installation”To start building an Aspire app that uses Meilisearch, install the 📦 CommunityToolkit.Aspire.Hosting.Perl NuGet package:
aspire add CommunityToolkit.Aspire.Hosting.PerlLearn more about aspire add in the command reference.
Or, choose a manual installation approach:
#:package CommunityToolkit.Aspire.Hosting.Meilisearch@*<PackageReference Include="CommunityToolkit.Aspire.Hosting.Perl" Version="*" />aspire add CommunityToolkit.Aspire.Hosting.PerlLearn more about aspire add in the command reference.
This updates your aspire.config.json with the Meilisearch hosting integration package:
{ "packages": { "CommunityToolkit.Aspire.Hosting.Perl": "*" }}Add Perl resource
Section titled “Add Perl resource”Add a Perl script resource in your AppHost.cs:
var builder = DistributedApplication.CreateBuilder(args);
builder.AddPerlScript("my-worker", "scripts", "Worker.pl") .WithCpanMinus() .WithPackage("Some::Module", skipTest: true) .WithLocalLib("local");
builder.Build().Run();Core concepts
Section titled “Core concepts”The integration provides two entry points for adding Perl resources:
| Method | Purpose |
|---|---|
AddPerlScript(name, appDirectory, scriptName) | Adds a Perl script (worker, CLI tool, etc.) |
AddPerlApi(name, appDirectory, scriptName) | Adds a Perl API server (e.g., Mojolicious daemon) |
Both create a PerlAppResource that appears in the Aspire dashboard. All subsequent configuration
methods (.WithCpanMinus(), .WithLocalLib(), etc.) chain off the resource builder.
The appDirectory Parameter
Section titled “The appDirectory Parameter”appDirectory is the anchor for all relative path resolution in the integration. It determines:
- The resource’s
WorkingDirectory— where Perl runs - Where
WithLocalLib("local")resolves to - Where cpanfile discovery happens (for
WithProjectDependencies) - The base for the script path
appDirectory is resolved relative to the AppHost project directory (the folder containing
the .csproj).
"." — AppHost-rooted
Section titled “"." — AppHost-rooted”When appDirectory is ".", the working directory is the AppHost project folder itself. Files like
cpanfile, cpanfile.snapshot, and the local/ directory all live alongside the .csproj:
DirectoryMyApp.AppHost
- AppHost.cs
- MyApp.AppHost.csproj
- cpanfile discovered here
- cpanfile.snapshot
Directorylocal WithLocalLib(“local”) resolves here
- lib/perl5
- Properties
Directoryscripts
- API.pl script path ”../scripts/API.pl”
"../scripts" — sibling folder
Section titled “"../scripts" — sibling folder”When appDirectory is "../scripts", the working directory shifts to a sibling scripts/ folder.
Everything resolves relative to that folder:
DirectoryMyApp.AppHost
- AppHost.cs
- MyApp.AppHost.csproj
DirectoryProperties/
- …
Directoryscripts working directory
- Worker.pl script path “Worker.pl”
Directorylocal WithLocalLib(“local”) resolves here
- lib/perl5
WithLocalLib
Section titled “WithLocalLib”.WithLocalLib("local") // relative path — resolved against appDirectory.WithLocalLib("/opt/lib") // rooted Unix-style path — used as-is.WithLocalLib("C:\\perl-lib") // rooted Windows path — used as-isWithLocalLib configures local::lib-style module isolation.
The path parameter is resolved relative to the resource’s working directory (appDirectory),
not relative to the AppHost project, unless the path is already rooted.
Implementation note: WithLocalLib path resolution uses Path.IsPathRooted(configuredPath).
If true, the value is used directly. If false, it is combined with the resource working
directory and converted to an absolute path.
What it sets
Section titled “What it sets”| Environment Variable | Value |
|---|---|
PERL5LIB | <resolved>/lib/perl5 |
PERL_LOCAL_LIB_ROOT | <resolved> |
PERL_MM_OPT | INSTALL_BASE=<resolved> |
PERL_MB_OPT | --install_base <resolved> |
These ensure that:
- Perl finds modules in the local directory at runtime (
@INC) - Package managers install modules into the local directory
- No
sudoor system-level permissions required
Resolution examples
Section titled “Resolution examples”appDirectory | WithLocalLib(...) | Resolved absolute path |
|---|---|---|
"." | "local" | <AppHost>/local |
"../scripts" | "local" | <AppHost>/../scripts/local |
"." | "/opt/perl-libs" | /opt/perl-libs (Linux/macOS) |
"." | "C:\\perl-libs" | C:\\perl-libs (Windows) |
Package management
Section titled “Package management”While I highly recommend you use cpanm or Carton, the integration aims to support three package managers and two installation strategies:
| Package Manager | Individual Packages | Project Dependencies |
|---|---|---|
| cpan (default) | ✅ .WithPackage("Module") | ❌ Not supported (auto-switches to cpanm when calling .WithProjectDependencies()) |
| cpanm (App::cpanminus) | ✅ .WithCpanMinus().WithPackage("Module") | ✅ .WithCpanMinus().WithProjectDependencies() |
| Carton | ❌ Not supported | ✅ .WithCarton().WithProjectDependencies() |
WithCpanMinus + WithPackage
Section titled “WithCpanMinus + WithPackage”Installs individual modules by name before the application starts.
builder.AddPerlScript("worker", "../scripts", "Worker.pl") .WithCpanMinus() .WithPackage("OpenTelemetry::SDK", skipTest: true) .WithLocalLib("local");What happens at startup:
- A child installer resource runs
cpanm --notest --local-lib <resolved>/local OpenTelemetry::SDK - The module is installed into
scripts/local/lib/perl5/ - After installation, the main script starts with
PERL5LIBpointing to the local directory
Resulting directory structure:
Directorymy-example
DirectoryMyExample.AppHost/
- AppHost.cs
- MyExample.AppHost.csproj
Directoryscripts working directory (appDirectory = ”../scripts”)
DirectoryWorker.pl
Directorylocal
Directorylib
Directoryperl5
DirectoryOpenTelemetry/
- SDK.pm
Options:
| Parameter | Effect |
|---|---|
force: true | Passes --force — reinstalls even if already present |
skipTest: true | Passes --notest — skips running the module’s test suite |
WithCpanMinus + WithProjectDependencies
Section titled “WithCpanMinus + WithProjectDependencies”Installs all modules listed in a cpanfile in the working directory.
builder.AddPerlApi("api", ".", "../scripts/API.pl") .WithCpanMinus() .WithProjectDependencies() .WithLocalLib("local");What happens at startup:
- The integration looks for
cpanfilein the working directory - Runs
cpanm --installdeps --notest .(with--local-libif configured) - All dependencies from the cpanfile are installed
Expected cpanfile location: <appDirectory>/cpanfile
WithCarton + WithProjectDependencies
Section titled “WithCarton + WithProjectDependencies”Carton is a dependency manager for Perl that provides
reproducible builds via a lock file (cpanfile.snapshot).
builder.AddPerlApi("api", ".", "../scripts/API.pl") .WithCarton() .WithProjectDependencies(cartonDeployment: false) .WithLocalLib("local");What happens at startup:
- The integration looks for
cpanfileand optionallycpanfile.snapshotin the working directory - Runs
carton install(orcarton install --deploymentifcartonDeployment: true) - Carton creates
local/adjacent to thecpanfile
Deployment mode (cartonDeployment: true): Installs exact versions from cpanfile.snapshot,
ensuring production builds match development. Fails if the snapshot is missing or out of date.
Resulting directory structure (appDirectory = ”.”):
Directorymy-example
DirectoryMyApp.AppHost working directory (appDirectory = ”.”)
- AppHost.cs
- MyApp.AppHost.csproj
- cpanfile
- cpanfile.snapshot
Directorylocal
Directorylib
Directoryperl5
- Mojolicious
Directoryscripts
- API.pl script path ”../scripts/API.pl”
Carton only supports project-level dependency installation. Calling .WithPackage() after .WithCarton() will throw an InvalidOperationException. If you need to install individual modules alongside Carton-managed dependencies, use .WithCpanMinus() on a separate resource.
WithPerlbrewEnvironment
Section titled “WithPerlbrewEnvironment”Perlbrew manages multiple Perl installations. This method configures the resource to use a specific perlbrew-managed Perl version.
builder.AddPerlScript("perlbrew-worker", "../scripts", "Worker.pl") .WithPerlbrewEnvironment("perl-5.42.0");What it configures:
- Resolves the Perl binary from the perlbrew installation
- Sets
PERLBREW_ROOT,PERLBREW_PERL, andPERLBREW_HOME - Prepends the perlbrew
bin/toPATH
Interaction with WithLocalLib: If .WithLocalLib("local") is chained, modules are installed
into the local directory, not the perlbrew tree. This keeps the perlbrew installation clean and
allows per-project isolation. WithLocalLib is optional when using perlbrew.
Common Pitfalls
Section titled “Common Pitfalls”WithLocalLib resolves relative to appDirectory, not the AppHost
Section titled “WithLocalLib resolves relative to appDirectory, not the AppHost”// appDirectory = "../scripts", WithLocalLib("local")// ✅ Resolves to: scripts/local/// ❌ Does NOT resolve to: MyApp.AppHost/local/If you expect the local/ folder next to your .csproj, set appDirectory to ".".
Choosing to skip WithLocalLib modifies shared Perl installs
Section titled “Choosing to skip WithLocalLib modifies shared Perl installs”It is valid to skip WithLocalLib if you intentionally want a shared/global module install.
That can be useful for common libraries on dev machines.
The tradeoff is that installs target your platform Perl distribution instead of a project-local folder. In practice this often means:
- Linux (especially OS-managed Perl): writes to system or user Perl paths and may require elevated permissions
- Windows: writes into the active Strawberry Perl or ActiveState Perl environment
This can be convenient, but it can also create drift across machines and affect unrelated projects. Proceed with caution.
Mixing WithCarton and WithPackage
Section titled “Mixing WithCarton and WithPackage”Carton manages all dependencies through cpanfile. Calling .WithPackage() after .WithCarton()
will throw an InvalidOperationException:
// ❌ This throws — Carton does not support individual module installationbuilder.AddPerlApi("api", ".", "api.pl") .WithCarton() .WithPackage("Some::Module");
// ✅ Instead, add the module to your cpanfile:// requires 'Some::Module';Script path is relative to appDirectory
Section titled “Script path is relative to appDirectory”The scriptName parameter is resolved relative to appDirectory. Don’t include the appDirectory
in the script path:
// ❌ Double-nests the pathbuilder.AddPerlScript("worker", "../scripts", "../scripts/Worker.pl");
// ✅ Correct — script path is relative to appDirectorybuilder.AddPerlScript("worker", "../scripts", "Worker.pl");cpanfile must be in the working directory
Section titled “cpanfile must be in the working directory”WithProjectDependencies looks for cpanfile in the resource’s working directory (appDirectory).
If your cpanfile is in a different location, adjust appDirectory accordingly.
cpanfile example
Section titled “cpanfile example”Use a cpanfile to declare project dependencies for WithProjectDependencies().
requires 'Mojolicious', '>= 9.0';requires 'OpenTelemetry::SDK';
on 'test' => sub { requires 'Test::More', '>= 1.302190';};Further reading: