Skip to content
Docs Try Aspire
Docs Try
⭐ Community Toolkit Perl Camel

This document explains how key hosting API calls map to on-disk directory layout, environment variable configuration, and runtime behavior.

To start building an Aspire app that uses Meilisearch, install the 📦 CommunityToolkit.Aspire.Hosting.Perl NuGet package:

Terminal
aspire add CommunityToolkit.Aspire.Hosting.Perl

Learn more about aspire add in the command reference.

Or, choose a manual installation approach:

C# — AppHost.cs
#:package CommunityToolkit.Aspire.Hosting.Meilisearch@*
XML — AppHost.csproj
<PackageReference Include="CommunityToolkit.Aspire.Hosting.Perl" Version="*" />

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();

The integration provides two entry points for adding Perl resources:

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

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

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”

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("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-is

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

Environment VariableValue
PERL5LIB<resolved>/lib/perl5
PERL_LOCAL_LIB_ROOT<resolved>
PERL_MM_OPTINSTALL_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 sudo or system-level permissions required
appDirectoryWithLocalLib(...)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)

While I highly recommend you use cpanm or Carton, the integration aims to support three package managers and two installation strategies:

Package ManagerIndividual PackagesProject 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()

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:

  1. A child installer resource runs cpanm --notest --local-lib <resolved>/local OpenTelemetry::SDK
  2. The module is installed into scripts/local/lib/perl5/
  3. After installation, the main script starts with PERL5LIB pointing 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:

ParameterEffect
force: truePasses --force — reinstalls even if already present
skipTest: truePasses --notest — skips running the module’s test suite

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:

  1. The integration looks for cpanfile in the working directory
  2. Runs cpanm --installdeps --notest . (with --local-lib if configured)
  3. All dependencies from the cpanfile are installed

Expected cpanfile location: <appDirectory>/cpanfile

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:

  1. The integration looks for cpanfile and optionally cpanfile.snapshot in the working directory
  2. Runs carton install (or carton install --deployment if cartonDeployment: true)
  3. Carton creates local/ adjacent to the cpanfile

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.

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, and PERLBREW_HOME
  • Prepends the perlbrew bin/ to PATH

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.

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.

Carton manages all dependencies through cpanfile. Calling .WithPackage() after .WithCarton() will throw an InvalidOperationException:

// ❌ This throws — Carton does not support individual module installation
builder.AddPerlApi("api", ".", "api.pl")
.WithCarton()
.WithPackage("Some::Module");
// ✅ Instead, add the module to your cpanfile:
// requires 'Some::Module';

The scriptName parameter is resolved relative to appDirectory. Don’t include the appDirectory in the script path:

// ❌ Double-nests the path
builder.AddPerlScript("worker", "../scripts", "../scripts/Worker.pl");
// ✅ Correct — script path is relative to appDirectory
builder.AddPerlScript("worker", "../scripts", "Worker.pl");

WithProjectDependencies looks for cpanfile in the resource’s working directory (appDirectory). If your cpanfile is in a different location, adjust appDirectory accordingly.

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: