# joho/godotenv: a feature-complete .env loader for Go

> godotenv does one job: read a .env file into the process environment or a map. The library is declared feature complete, so the interesting questions are about precedence, parsing rules and when you should reach for something else.

**joho/godotenv** — A Go port of Ruby's dotenv library (Loads environment variables from .env files)

- Repository: https://github.com/joho/godotenv
- Website: http://godoc.org/github.com/joho/godotenv
- Stars: 10,649 · Forks: 462
- Language: Go
- License: MIT
- Published: 2026-09-21 · Updated: 2026-09-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/joho-godotenv

## What joho/godotenv actually solves

The README frames the problem by quoting the original Ruby dotenv project: configuration that changes between deployment environments should live in environment variables, but setting those variables by hand on development machines and CI servers where several projects run is impractical. godotenv closes that gap for Go. It reads a .env file and puts the values into the process environment, or hands them back as a map if you would rather not touch the environment at all.

The intended user is a Go developer bootstrapping a service locally, or a team whose CI runs many projects on one machine. The library is a port of Ruby's dotenv, built from that project's tests and fixtures, which matters because it tells you where the semantics come from. It is not an application configuration framework. There is no schema, no type coercion, no hot reload. If you want those, this is the wrong tool.

## How godotenv parses and applies values

The mechanism is deliberately narrow. A file is read, parsed into a map of string keys to string values, and then either written into the process environment or returned to the caller. The same parser backs four entry points: Load for files, Read for a map, Parse for an io.Reader, and Unmarshal for a string. That last pair is what makes remote configuration possible without a local file, since the README shows feeding a reader or a string in place of disk content.

The parser accepts more than plain KEY=value lines. Comments on their own line and at the end of a line are valid, export prefixes are stripped, and a YAML-ish FOO: bar form is accepted. Key names follow the Node dotenv charset since v1.4.0, so keys containing a hyphen parse cleanly. Ruby's dotenv only allows [A-Za-z0-9_.], so a key like MY-VAR works here and in Node but errors under Ruby's implementation. That is a real compatibility decision, not an accident, and it is the kind of detail you only discover when a key silently fails somewhere else.

Precedence is the part most people get wrong. Existing environment variables take precedence over values loaded later, so Load never overwrites what the process already has. The README's convention for multiple environments is to read an app-specific variable such as FOO_ENV, default it to development, then load .env, .env.<env>, .env.local and .env.<env>.local in that order. Overload exists to invert this and overwrite existing values, and the README itself says to use it with caution. The ordering logic is yours to write; the library does not discover files for you.

## Installing godotenv and loading a first file

The README gives three installation routes. The plain library is the common one. There is also a tool dependency form that requires Go 1.24 or later, and a bin command install for the CLI. Note that the module's go.mod still declares go 1.13, which is the minimum for the library itself rather than for the tool dependency route.

```bash
go get github.com/joho/godotenv
```

That adds the module to your go.mod. For the command-line binary instead, the README uses:

```bash
go install github.com/joho/godotenv/cmd/godotenv@latest
```

With the library in place, create a .env file in the project root and load it at startup. The README's example reads two keys and then uses them:

```go
package main

import (
    "log"
    "os"

    "github.com/joho/godotenv"
)

func main() {
  err := godotenv.Load()
  if err != nil {
    log.Fatal("Error loading .env file")
  }

  s3Bucket := os.Getenv("S3_BUCKET")
  secretKey := os.Getenv("SECRET_KEY")
}
```

After this, os.Getenv returns the values from .env for any key the process did not already have. If you would rather not wire the call in yourself, the autoload package does it on import:

```go
import _ "github.com/joho/godotenv/autoload"
```

For the CLI, the README's invocation takes a file and a command. Without -f it falls back to .env in the working directory, and without -o it will not override existing environment variables.

```bash
godotenv -f /some/path/to/.env some_command with some args
```

## Silent precedence and the feature-complete freeze

The sharpest limitation is precedence. Because existing environment variables win, a stale variable exported in your shell quietly beats the .env file you just edited. Nothing warns you. The README documents the rule and offers Overload as the escape hatch, but Overload inverts the behaviour for every key, not the one you meant to fix. If your debugging session is going in circles, check the shell environment before you check the parser.

The second constraint is governance. The README states the library has been declared feature complete, referencing issue #182, and will not accept issues or pull requests that add functionality or break the API. Accepted contributions are parsing compatibility with Ruby's and Node's dotenv, keeping up with the Go ecosystem, and bug fixes within the library's stated purpose. Code changes without tests and references to peer dotenv implementations are rejected. That is an unusually clear boundary, and it means you should evaluate godotenv as a finished component rather than one that will grow with your needs.

The bin version carries its own caveat: the README says there is CI for linuxish and Windows environments but makes no guarantees about the bin version working on Windows. If your pipeline depends on the CLI on Windows, that sentence is the one to weigh.

## godotenv vs Viper and os.Getenv

The alternative people search for is Viper, and the difference is scope rather than quality. Viper is a configuration system: it merges sources, handles formats and types, and layers defaults, files, environment and flags. godotenv does none of that. It reads one or more .env files, applies them to the environment in a documented order, and stops. If your service needs typed nested configuration assembled from several places, godotenv will not get you there and you should not try to make it.

The other comparison is os.Getenv alone. If your deployment already injects environment variables, godotenv adds nothing at runtime; its value is the local and CI bootstrap step where no one wants to export a dozen variables by hand. It also gives you Read, Parse and Unmarshal when you want the values as data instead of process state, which plain os.Getenv cannot do. The README also documents the reverse direction: Unmarshal a string and Write the resulting map back to a file, or Marshal it to a string. That makes godotenv usable for generating .env files, not only consuming them.

## Maintenance, licence and upgrade cost

The repository is not archived and the last push was on 2026-08-04. The release history is worth reading before you pin a version: v1.6.0-pre.2 dates from 2024-12-16, while v1.6.0-pre.3 and v1.6.0-pre.4 both landed in May 2026. Those are pre-release tags, so a stable v1.6.0 is not indicated by the tags listed. If you depend on a released version, check which tag you are actually pulling.

The licence is MIT, which permits use, modification and redistribution provided the copyright notice and permission notice are retained. That is a permissive licence, but it is not legal advice and your organisation's policy on attribution in distributed binaries is the thing to check.

Upgrade cost is low by design. The public surface is a handful of functions, the module has no dependencies in its go.mod, and the maintainers have committed to not breaking the API. The realistic upgrade risk is behavioural rather than structural: parser compatibility changes, such as the Node charset alignment in v1.4.0, can alter how an unusual key parses. Pin the version, and read the release notes for parser changes before bumping.

## Conclusion

Adopt godotenv if you want a small, settled dependency that turns a .env file into process environment variables for local development, tests or a twelve-factor bootstrap, and if you are happy with the Node-style key charset it has matched since v1.4.0. Do not adopt it as a configuration framework: there is no reload, no watching, no layered config merging beyond the load order you write yourself, and the maintainers have declared the library feature complete, so new functionality is not on the table. Before you commit, verify two things in your own tree: which files your load order actually reads, and whether any key in your .env uses characters outside the Node charset. If you need typed configuration and merging, look at a dedicated config library instead.

## FAQ

### How do I install joho/godotenv?

For library use, run go get github.com/joho/godotenv. For the command-line binary, the README uses go install github.com/joho/godotenv/cmd/godotenv@latest, and there is also a tool dependency form requiring Go 1.24 or later.

### How do I use godotenv in a Go program?

Call godotenv.Load() at startup, then read values with os.Getenv. The README also shows an autoload package that reads .env on import, and Read, Parse and Unmarshal for getting the values as a map instead of touching the environment.

### Why is godotenv not loading my values?

Existing environment variables take precedence over values loaded later, so a variable already exported in your shell will win over the .env file. The README documents godotenv.Overload() to overwrite existing values instead, and says to use it with caution.

### Does godotenv overwrite environment variables that are already set?

No. By default existing envs take precedence over envs loaded later, so Load only supplants them. Overload is the documented way to defy that convention and overwrite instead.

### Can godotenv write a .env file as well as read one?

Yes. The README shows Unmarshal turning a string into a map, then Write sending that map to a file path, or Marshal returning the same content as a string.

### Does godotenv accept the same key names as Ruby's dotenv?

No. Ruby's dotenv only allows [A-Za-z0-9_.] in key names, while godotenv has matched the Node charset since v1.4.0, so keys containing a hyphen parse here and in Node but error under Ruby's dotenv.

## Sources

- [joho/godotenv on GitHub](https://github.com/joho/godotenv)
- [License: MIT](https://github.com/joho/godotenv/blob/main/LICENSE)
- [Project website](http://godoc.org/github.com/joho/godotenv)
- [README](https://github.com/joho/godotenv/blob/main/README.md)
- [Releases](https://github.com/joho/godotenv/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/joho-godotenv
