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

> Discover how Akash Console manages environment variables using the @akashnetwork/env-loader package. Learn about its deterministic loading, Zod validation, and framework-agnostic integration.

- Repository: [Akash Network/console](https://github.com/akash-network/console)
- Tags: internals
- Published: 2026-02-24

---

**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`](https://github.com/akash-network/console/blob/main/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`](https://github.com/akash-network/console/blob/main/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

```javascript
// 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`](https://github.com/akash-network/console/blob/main/packages/logging/src/config/env.config.ts) validates log levels and output formats:

```typescript
// 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`](https://github.com/akash-network/console/blob/main/apps/notifications/src/main.ts), the import statement triggers environment loading immediately:

```typescript
// 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`:

```typescript
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`](https://github.com/akash-network/console/blob/main/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:

```dotenv

# env/.env.local

MY_NEW_FEATURE=true

```

2. **Extend the Zod schema** in the relevant package:

```typescript
// 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")
});

```

3. **Consume the validated config**:

```typescript
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`:

```typescript
// 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`](https://github.com/akash-network/console/blob/main/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.