Skip to content

Releases: tetratelabs/func-e

v1.6.0

19 May 09:30
33bfb64

Choose a tag to compare

func-e 1.6.0 gives you yesterday's envoy main build via the "dev" version

Before, testing pre-release Envoy with func-e meant compiling it yourself or using Docker, which only worked on Linux. Now you can install and run dev builds on all platforms the same way you would any tagged version.

$ ENVOY_VERSION=dev func-e run --version                                                              
downloading https://archive.tetratelabs.io/envoy/download/dev/envoy-dev-darwin-arm64.tar.xz
starting: /Users/codefromthecrypt/.local/share/func-e/envoy-versions/dev/bin/envoy with logs in /Users/codefromthecrypt/.local/state/func-e/envoy-runs/20260518_170024_807

/Users/codefromthecrypt/.local/share/func-e/envoy-versions/dev/bin/envoy  version: abbadd905fa486ff1085cf3bbe4f3b73eb6dd8e0/1.39.0-dev/Clean/RELEASE/BoringSSL

$ func-e versions -a                                                                                  
  dev 2026-05-18 (a8d396eb)
  1.38.0 2026-04-23
  1.37.2 2026-04-10
-- snip--

This works because envoy's version manifest was recently updated with a special "dev" key backed by a daily pipeline job, which archives linux from envoy's docker image, and builds macos from the same SHA.

Updating the "dev" build with the "dev-latest" alias.

dev installs on demand like all other versions and stays put once pulled. The alias dev-latest compares the remote release date against the local install and re-downloads only when the build has changed. func-e use dev-latest persists "dev" in the version file, so it is a one-shot refresh, not a stored preference.

Why Use An Envoy "dev" Build?

Envoy releases are worth the wait, but they land about once a quarter. The dev build is for the gap between “merged” and “released”: you can try current Envoy mainline without building Envoy yourself or relying on a Linux-only Docker workflow.

For example, Envoy 1.39-dev already has new dynamic-module support for request-aware upstream selection. A dynamic module can parse a request, write selected_backend or selected_endpoint into request state, and have the cluster read it while choosing the upstream host. That removes the need to pass routing decisions through synthetic headers or route re-selection.

Use dev when you need to test an unreleased feature, validate upgrade impact early, or give feedback before the next Envoy release is cut. Use tagged versions for normal production runs.

v1.5.0

08 May 08:49
cf9478b

Choose a tag to compare

func-e 1.5 introduces new run options in support of emerging Envoy programming paradigms. Notably, you can control all HTTP operations of func-e, such as its Envoy binary registry and admin checks. You can safely run and check multiple envoys at the same time. You can BYO Envoy to test a feature you are working on! You can also use some "almost unnecessary" go tricks in your unit tests. Give it a run!

BYO Envoy

More are programming Envoy, lately. Envoy introduced Dynamic Modules reach beyond custom HTTP filters. Cert validators can change trust decisions, load-balancers can change endpoint choice, clusters can change upstream behavior, and tracers can own propagation. You have the full power of envoy, decoupled from the binary, with no extra hop tax.

So, what's the catch? Dynamic Modules are moving fast! Envoy has a quarterly release schedule, but those improving it want to move faster and test in an integrated environment.

You might ask.. wait, func-e is just a launcher binary. How does this fit in? While not obvious to all, func-e is used as a "host mode" option for systems like Envoy Gateway (EG) and Built on Envoy (BOE). Go programs like these use func-e as a library for orchestrating the process and its config dynamically.

Before 1.5, func-e would only allow you to use a binary registry (defaults to all releases). So, if you are developing envoy and want to see if the next version works with Envoy Gateway, or a new Dynamic Module ABI works with BOE.. hacks were needed. Sometimes this gets deeper. For example Envoy AI Gateway (AIGW) also uses func-e as a library. We accidentally and ironically became a devops tension! Hence BYO Envoy is the way out.

So, how do you use it? Let's say you built your own envoy via bazel build envoy on your laptop as you are developing the next awesome dynamic modules hoot. Pass the "/path/to/bazel-bin/source/exe/envoy-static" to func-e. It will disable the registry and let you run that binary instead.

Embedders like BOE will plumb via api.EnvoyPath, those via scripts the ENVOY_PATH variable. That's it.

Smarter AdminClient

We have another helper for embedders called AdminClient. This gives a programatic way to navigate to func-e's managed Envoy process and magically find its admin server. This is neat for a lot of reasons, but primarily it drives HEALTHCHECK impls in projects like Envoy AI Gateway (AIGW) standalone mode. We had some imprecision here as it assumed the owning process wasn't launching other processes.

Built on Envoy (BOE) broke that assumption because not only does it launch Envoy (via func-e) and work with Dynamic Modules, it also works with ExternalProcessor (ext_proc). ext_proc is a grpc-based extension model that is more forgiving programming wise than ABI extensions, but costs you a process to manage and a hop (even if UDS). So, in that mode BOE launches at least two processes: envoy and an ext_proc filter.

@nacx figured all this out. Basically an AdminClient based HEALTHCHECK would be unreliable as it could mistake an ext_proc as an admin server and fail. To solve this, we added a marker to the signature we launch envoy with, notably the RunID which ties to its configuration and log directory on disk. If you pass api.RunID, AdminClient will fail until it can find the exact envoy associated with it. If you don't it will still be more precise than before, and avoid the "ext_proc" clash.

Here's a sketch of how a tighter health check can work in BOE, allowing it to run multiple envoys at some point.

func doHealthcheck(ctx context.Context, boePid int, runID string, logger *slog.Logger) error {
	// This could be called for any given envoy run managed by func-e in the process!
	adminClient, err := admin.NewAdminClient(ctx, boePid, api.RunID(runID))
	if err != nil {
		logger.Error("Failed to find Envoy admin server", "error", err)
		return err
	}
	return adminClient.IsReady(ctx)
}

Almost unnecessary go ridiculousness

At least one contributor to func-e is obsessed with Go synctest library. This is because it lets you have an easy path to control clocks, look for lost goroutines, and test neat things such as retry behavior without wall clock time. func-e's internals have been rewritten so that folks integrating with it as a library can also contain func-e in a synctest "bubble". You do this by swapping out the things that cause real processes or network IO, notably via api.RunFunc and the new api.HTTPTransport.

Here's a working example of an http client and server that don't actually use the network!

// TestRun shows func-e works inside synctest.Test without real network I/O.
func TestRun(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		// Pipe-backed server keeps all I/O in-process, compatible with synctest.
		var actualURL *url.URL
		ts := httptest.NewServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			actualURL = r.URL
			w.WriteHeader(http.StatusNotFound)
		}))

		// Route func-e's HTTP traffic through the test server via its transport.
		versionsURL := ts.URL + "/envoy-versions.json"
		err := func_e.Run(t.Context(), []string{"--config-yaml", "foo"},
			api.HomeDir(t.TempDir()), api.EnvoyVersionsURL(versionsURL), api.HTTPTransport(ts.Client().Transport))
		require.Error(t, err)
		require.Equal(t, "/envoy-versions.json", actualURL.String())
	})
}

A little help from our friends

func-e is not alone in bridging Envoy goodness to more developers, and we've had a lot of help from a friend project pyvoy. Pyvoy is a Python application server implemented in Envoy... yes you heard that right. uv run pyvoy my.module:app Envoy is the listener for Python middleware. This is an incredible example of the power of clever people using Envoy Dynamic Modules.

The lead @anuraaga has contributed in all areas of Envoy, and directly to this func-e release. Without him, the code would be worse and less fun. If you have a use case where you want the power of envoy and the easy of python. Try out pyvoy!

v1.4.0

28 Jan 23:28
77c6bfb

Choose a tag to compare

func-e 1.4.0 updates all versions, notably Go 1.25 and Envoy 1.37

This is a minor bump on account of the Go version update.

v1.3.0

13 Oct 18:16
f17d531

Choose a tag to compare

What's Changed

func-e v1.3.0 uses config_home, data_home, state_home and runtime_dir conventions defined in XDG.

Instead of the now deprecated api.HomeDir()/$FUNC_E_HOME, we have four tiers:

  • api.ConfigHome()/$FUNC_E_CONFIG_HOME: Configuration files like version preferences (default: ~/.config/func-e)
  • api.DataHome()/$FUNC_E_DATA_HOME: Envoy binaries and downloaded data (default: ~/.local/share/func-e)
  • api.StateHome()/$FUNC_E_STATE_HOME: Run logs and persistent state (default: ~/.local/state/func-e)
  • api.RuntimeDir()/$FUNC_E_RUNTIME_DIR: Ephemeral temporary files (default: /tmp/func-e-${UID})

This separation aligns with XDG Base Directory Specification principles:

  • Configuration (envoy-version) is distinct from data (binaries)
  • State (logs) is separate from ephemeral temporary files (admin addresses)
  • Each directory can be independently configured for different storage tiers
  • Docker deployments can easily map volumes to appropriate directories

Run ID

This also adds api.RunID()/$FUNC_E_RUN_ID to override the formerly epoch timestamp with something else for predictable paths in Docker which only support one run typically. For example, we can set it to zero and always know where the logs are.

Here are details on the implementation:

  • --run-id: Sets custom run identifier for current execution (or $FUNC_E_RUN_ID)
  • Default: YYYYMMDD_HHMMSS_UUU (human-readable timestamp)
  • Previous: epoch nanoseconds (less readable)
  • Enables predictable paths for docker (e.g. setting to zero)

Migration

func-e begins immediately to use these conventions except in cases where the deprecated $FUNC_E_HOME was overridden, such as envoyproxy/gateway. This allows tools to migrate on their own schedule.

Unless you set the now deprecated api.HomeDir()/$FUNC_E_HOME, files end up in the new layout described above:

  • Version preference: $FUNC_E_CONFIG_HOME/envoy-version (was: $FUNC_E_HOME/version)
  • Binaries: $FUNC_E_DATA_HOME/envoy-versions/{version}/bin/envoy (was: $FUNC_E_HOME/versions/{version}/bin/envoy)
  • Run logs: $FUNC_E_STATE_HOME/envoy-runs/{runID}/stdout.log (was: $FUNC_E_HOME/runs/{epoch}/stdout.log)
  • Admin address: $FUNC_E_RUNTIME_DIR/{runID}/admin-address.txt (was: $FUNC_E_HOME/runs/{epoch}/admin-address.txt)

Experimental

This release also includes experimental features RunMiddleware AdminClient and StartupHook. Do not use these unless your project is a binary, as doing so in middleware can create version lockups.

Full Changelog: v1.2.0...v1.3.0

v1.2.0

22 Jul 00:52
c1bbfa9

Choose a tag to compare

func-e 1.2.0 removes shutdown hooks and migrates api.Run to func_e.Run

Replacing shutdown hooks with startup hooks

Shutdown hooks were very problematic in practice. To attempt them meant interfering with signal and context cancelation propagation, for limited value. Instead, we now collect the majority of info needed for XDS troubleshooting after reaching the startup line of envoy. This gives us most of the value without the problems.

As a consequence, func-e no longer self-zips its run directory on shutdown. However, this is not common practice with other tools and folks can do that on own as necessary.

api.Run -> func_e.Run

In a previous release, we accidentally added an implementation to our api package, causing package cycle problems. This moves the impl from api.Run to func_e.Run. This also solves a problem in the former implementation which routed library commands through the cmd runner.

We made a breaking change because at the moment, there is only one user of this API and maintainers of func-e also work on that. This allows us to remove the problem for future library users before it gets too difficult to remove.

A side effect is we moved main.go -> cmd/func-e/main.go which allows the root package to be used as a library.

Tighter contract on api.RunFunc

Now, that the ai package is not also an implementation, we have a chance to clean up some interface issues. Before, if you canceled an envoy run, the error returned was vague. We now follow envoy conventions, which return exit code 0 on interrupt. To translate this to go, it means api.RunFunc documents nil error in these cases, and func_e.Run has tests to prove it.

Full Changelog: v1.1.5...v1.2.0

v1.1.5

03 Jul 17:38

Choose a tag to compare

This is mostly a maintenance release, especially focused on the internal refactoring.

v1.1.4

06 Jan 03:00
68c7393

Choose a tag to compare

func-e 1.1.4 is a maintenance release built with Go 1.19.

Note: Recent versions of Envoy no longer run on the default build of CentOS 8, and attempts will result in the following error /lib64/libm.so.6: version 'GLIBC_2.29' not found. This is not an issue func-e can resolve, so please contact Envoy with platform support concerns.

v1.1.3

08 May 05:21
00d08b6

Choose a tag to compare

func-e 1.1.3 is a maintenance release that was built with Go 1.18

v1.1.2

22 Jan 01:55
ffeecb5

Choose a tag to compare

func-e 1.1.2 contains only version updates. This release is only to inform people that Envoy 1.21 is out, and that is the first version that allows M1 Macs to run without emulation.

Many thanks to @XuehaiPan who stuck in there several weeks until Envoy 1.21 could build on Homebrew.

FYI: You don't need to upgrade func-e to use Envoy 1.21, but since you are looking anyway, why not!

v1.1.1

01 Dec 23:39
d08a2f0

Choose a tag to compare

func-e 1.1.1 includes no user facing changes, but you should still read this!

Envoy 1.20.1 works on macOS 12

If you aren't running OS/x aka macOS aka Darwin, you can skip this part.

Shortly after macOS 12 (Monterey) released in late October, users complained Envoy no longer works, resulting in a fix shortly after. However, this fix wasn't released until over a month later (yesterday) in 1.20.1. Anyone who upgraded to macOS 12 was out of commission for over a month, regardless of if they used homebrew or func-e to run envoy. While that period was regrettable, it is over now. Envoy 1.20.1 is available and that works on macOS 12. Note that 1.20.1 is the only version that works, as the patch wasn't made to other versions (such as 1.19.x).

Note: you do not have to upgrade func-e to use Envoy 1.20.1. That said, while you are here, you might as well upgrade!

Minor changes

The actual func-e release is only patches and minor maintenance. Thanks to @codefromthecrypt and @tranngoclam for the help on this.