Hugo Environment-Specific Builds: How Production and Development Modes Work

Hugo handles environment-specific builds through a hierarchical configuration system that checks the --environment CLI flag, HUGO_ENVIRONMENT environment variable, or environment config key, defaulting to "development" when none are specified.

The gohugoio/hugo static site generator treats the build environment as a first-class configuration value that influences cascade rules, template rendering, and asset pipeline behavior. Understanding how Hugo distinguishes between production and development environments allows developers to conditionally enable features like minification, fingerprinting, and debugging across different deployment contexts.

Setting the Build Environment

Hugo accepts the target environment through three distinct input methods, evaluated in a specific priority order during the build initialization phase.

Configuration File

Define the environment explicitly in your site configuration using the environment key. In config.yaml or config.toml:

environment: production

The value is read by cfg.Environment() from the loaded configuration and stored in config.AllProvider.Environment(), as defined in [config/configProvider.go](https://github.com/gohugoio/hugo/blob/master/config/configProvider.go#L34-L38).

Environment Variables

Set HUGO_ENVIRONMENT or the shorthand HUGO_ENV before invoking the Hugo binary:

export HUGO_ENVIRONMENT=production
hugo

These variables are processed during command execution and injected into external tool environments via GetExecEnviron in [common/hugo/hugo.go](https://github.com/gohugoio/hugo/blob/master/common/hugo/hugo.go#L102-L115).

CLI Flag Override

The --environment (or -e) flag takes highest precedence and overrides all other sources:

hugo server --environment=production

This flag is parsed in [commands/commandeer.go](https://github.com/gohugoio/hugo/blob/master/commands/commandeer.go#L546-L550) and stored in c.r.environment, then injected into the configuration during build setup in [commands/hugobuilder.go](https://github.com/gohugoio/hugo/blob/master/commands/hugobuilder.go#L1084-L1100).

Default Fallback

When no environment is specified via flag, variable, or config, Hugo falls back to the constant EnvironmentDevelopment ("development"). The production equivalent is defined as EnvironmentProduction ("production"), both declared in [common/hugo/hugo.go](https://github.com/gohugoio/hugo/blob/master/common/hugo/hugo.go#L45-L48).

Runtime Environment Usage

Once determined, the environment value propagates through multiple subsystems to enable conditional behavior.

Cascade Rules and Page Matching

The PageMatcher logic in [resources/page/page_matcher.go](https://github.com/gohugoio/hugo/blob/master/resources/page/page_matcher.go#L55-L97) evaluates environment glob patterns when applying cascade front-matter rules. The matcher checks:

if m.Environment != "" {
    g, err := hglob.GetGlob(m.Environment)
    if err == nil && !g.Match(environment) { return false }
}

This allows configuration like:

cascade:
  - target:
      environment: production
    minify: true

Template Access

Templates retrieve the current environment through the global Hugo object:

{{ .Site.Hugo.Environment }}  // Returns "development" or "production"

This method is implemented in [resources/page/hugoinfo.go](https://github.com/gohugoio/hugo/blob/master/resources/page/hugoinfo.go#L59-L64), where HugoInfo.Environment() forwards to the underlying configuration provider.

Asset Pipeline Integration

External build tools such as PostCSS and Babel receive the environment via GetExecEnviron, which explicitly sets HUGO_ENVIRONMENT and HUGO_ENV in the process environment. This allows asset pipelines to switch behavior based on the build mode without additional configuration.

Server Confirmation

When running hugo server, the active environment is printed to standard output for confirmation, as implemented in [commands/server.go](https://github.com/gohugoio/hugo/blob/master/commands/server.go#L249-L250).

Environment Resolution Flow

The resolution process follows a strict hierarchy during site initialization:

  1. Command Parsingcommands/hugobuilder.go evaluates the --environment flag first, falling back to environment variables and configuration file values.
  2. Configuration Loadingconfig.LoadConfigFromDir receives the final environment string and stores it in the AllProvider implementation.
  3. Runtime Propagation – The value becomes accessible via:
    • .Site.Hugo().Environment() for templates
    • PageMatcher.Environment for cascade matching
    • GetExecEnviron for external tool execution

Practical Implementation Examples

Switching Analytics in Templates

Use the environment to conditionally load production-only analytics scripts:

{{ if eq .Site.Hugo.Environment "production" }}
  <script async src="https://analytics.example.com/script.js"></script>
{{ end }}

Production-Only Minification

Apply minification exclusively to production builds using cascade rules:

cascade:
  - target:
      environment: production
    build:
      minify: true

External Tool Configuration

Configure PostCSS to enable purgecss only in production by checking process.env.HUGO_ENVIRONMENT:

// postcss.config.js
const purgecss = require('@fullhuman/postcss-purgecss');

module.exports = {
  plugins: [
    ...(process.env.HUGO_ENVIRONMENT === 'production' ? [purgecss()] : [])
  ]
};

Summary

  • Hugo accepts environment configuration via the --environment CLI flag, HUGO_ENVIRONMENT environment variable, or environment config key, falling back to "development" by default.
  • The constants EnvironmentProduction and EnvironmentDevelopment in common/hugo/hugo.go define the canonical string values.
  • commands/hugobuilder.go orchestrates the environment resolution during build initialization.
  • Cascade rules in resources/page/page_matcher.go use glob matching to apply environment-specific front matter.
  • Templates access the environment via .Site.Hugo.Environment, which retrieves the value from the configuration provider.
  • External asset pipelines receive the environment through HUGO_ENVIRONMENT and HUGO_ENV variables injected by GetExecEnviron.

Frequently Asked Questions

How do I check the current Hugo environment in a template?

Access the environment through the global Hugo object using {{ .Site.Hugo.Environment }}. This returns the string "development" or "production" (or any custom value you set) and is implemented in resources/page/hugoinfo.go as a wrapper around the configuration provider's Environment() method.

What is the priority order for Hugo environment configuration?

Hugo evaluates sources in the following priority: first the --environment CLI flag, then the HUGO_ENVIRONMENT or HUGO_ENV environment variables, then the environment key in your configuration file, and finally defaults to "development" if none are provided. This hierarchy is enforced in commands/hugobuilder.go during the build initialization phase.

Can I use custom environment names beyond production and development?

Yes, while Hugo defines EnvironmentProduction and EnvironmentDevelopment constants in common/hugo/hugo.go, you can specify any arbitrary string as the environment value. The PageMatcher in resources/page/page_matcher.go uses glob patterns, allowing you to target specific environments or groups (e.g., staging) in your cascade rules and conditional template logic.

How does Hugo pass the environment to PostCSS and other external tools?

Hugo injects the current environment into external process environments through the GetExecEnviron function in common/hugo/hugo.go. This function explicitly sets both HUGO_ENVIRONMENT and HUGO_ENV variables before spawning tools like PostCSS or Babel, allowing these pipelines to branch their behavior based on the build mode without requiring separate configuration files.

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 →