How Akash Console Manages Environment Variables: The @akashnetwork/env-loader Architecture

Akash Console centralizes environment variable management through a dedicated @akashnetwork/env-loader package that implements a deterministic loading sequence, Zod-based validation, and framework-agnostic integration across all services.

Managing configuration across a multi-service monorepo requires strict consistency. The Akash Console repository solves this through a centralized loading pattern that ensures every service—from the provider console to the notifications indexer—accesses validated, typed environment variables through a single source of truth.

Centralized Loading with @akashnetwork/env-loader

Every service in the Akash Console ecosystem imports @akashnetwork/env-loader at the entry point. This triggers a deterministic sequence defined in packages/env-loader/src/index.js that merges multiple environment files into process.env before any application code executes.

File Discovery and Loading Order

The loader implements a cascading priority system to resolve configuration values. According to lines 23-41 of packages/env-loader/src/index.js, the loader searches for files in this specific order:

  1. Local override (.env.local) – loaded only when DEPLOYMENT_ENV is undefined
  2. Deployment-specific (env/.env.<DEPLOYMENT_ENV>) – targeted environment configuration
  3. Network-specific (env/.env.<NETWORK>) – blockchain network settings
  4. Generic fallback (env/.env) – base defaults shared across all environments
// packages/env-loader/src/index.js
if (!process.env.DEPLOYMENT_ENV) {
  config("../../.env.local");
}
config(`env/.env.${process.env.DEPLOYMENT_ENV || "local"}`);
config(`env/.env.${process.env.NETWORK}`);
config("env/.env");

The implementation uses @dotenvx/dotenvx to parse and inject variables. After loading, the loader logs which files were successfully applied (lines 42-43), providing visibility into the active configuration source.

Type-Safe Validation with Zod

Raw environment variables are untyped strings. Akash Console enforces runtime type safety through Zod schemas that parse process.env and extract validated configuration objects.

Each package defines its own validation schema. For example, the logging package in packages/logging/src/config/env.config.ts validates log levels and output formats:

// packages/logging/src/config/env.config.ts
const envSchema = z.object({
  LOG_LEVEL: z.enum(["fatal","error","warn","info","debug","trace"]).optional().default("info"),
  STD_OUT_LOG_FORMAT: z.enum(["json","pretty"]).optional().default("json")
});
export const envConfig = envSchema.parse(process.env);

Services import the parsed envConfig object rather than accessing process.env directly. This guarantees that all runtime configuration is typed and validated, with sensible defaults applied at the schema level.

Framework Integration Patterns

The env-loader operates independently of application frameworks, making it compatible with NestJS, Next.js, and plain Node.js services.

NestJS Integration

Backend services like the notifications indexer import the loader before bootstrapping the NestJS application. In apps/notifications/src/main.ts, the import statement triggers environment loading immediately:

// apps/notifications/src/main.ts
import "@akashnetwork/env-loader";   // Triggers loading before NestJS starts
import { NestFactory } from '@nestjs/core';

The NestJS ConfigModule then reads from the already-populated process.env:

import { ConfigModule, ConfigService } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
  ],
  providers: [
    {
      provide: 'MY_SERVICE',
      useFactory: (cfg: ConfigService) => ({
        apiUrl: cfg.get<string>('API_URL'),
      }),
      inject: [ConfigService],
    },
  ],
})
export class MyModule {}

Next.js Integration

Frontend applications like the provider console initialize the loader in next.config.js, ensuring environment variables are available during both build and runtime phases.

Practical Implementation Examples

Adding a New Environment Variable

To introduce a new configuration parameter across the stack:

  1. Declare the value in the appropriate .env file:

# env/.env.local

MY_NEW_FEATURE=true
  1. Extend the Zod schema in the relevant package:
// packages/logging/src/config/env.config.ts
const envSchema = z.object({
  LOG_LEVEL: z.enum(["fatal","error","warn","info","debug","trace"]).optional().default("info"),
  MY_NEW_FEATURE: z.string().optional().default("false")
});
  1. Consume the validated config:
import { envConfig } from "@akashnetwork/logging/src/config";

if (envConfig.MY_NEW_FEATURE === "true") {
  // Enable experimental behavior
}

Manual Loader Invocation

For scripts or utilities that run outside the standard service architecture, manually trigger the loader to populate process.env:

// scripts/print-env.js
require("@akashnetwork/env-loader");
console.log(process.env.API_URL);  // Now contains merged variables

Summary

  • Single source of truth: The @akashnetwork/env-loader package handles all environment file discovery and loading across the monorepo.
  • Deterministic priority: Local overrides take precedence over deployment-specific files, which override network-specific files, which override base defaults.
  • Runtime validation: Zod schemas in packages like packages/logging/src/config/env.config.ts enforce type safety and provide default values.
  • Framework agnostic: Works with NestJS modules, Next.js applications, and vanilla Node.js scripts by populating process.env before framework initialization.
  • Zero runtime overhead: After initial load, services read from cached config objects without additional file I/O.

Frequently Asked Questions

What is the exact loading order for environment files in Akash Console?

The loader checks files in this sequence: first .env.local (only if DEPLOYMENT_ENV is unset), then env/.env.<DEPLOYMENT_ENV>, followed by env/.env.<NETWORK>, and finally the generic env/.env. This hierarchy ensures local development overrides production defaults while maintaining deployment-specific configurations.

How does Akash Console validate that required environment variables exist?

Each package defines a Zod schema that parses process.env at import time. If required variables are missing or malformed, schema.parse() throws a validation error immediately upon service startup, preventing runtime failures from undefined configuration values.

Can I use the env-loader in non-NestJS applications?

Yes. The loader is framework-agnostic. Import @akashnetwork/env-loader in any Node.js script, Express application, or Next.js configuration file to populate process.env before your application logic executes.

Where should I add new environment variables in the codebase?

Add the variable declaration to the appropriate .env file in the env/ directory, then extend the relevant Zod schema (typically in packages/<name>/src/config/env.config.ts) to parse and validate the new value with appropriate defaults.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →