---
title: "Migrate from Envoy"
url: "/guides/proxy/migrate-from-envoy/"
kind: "guide"
summary: "Run an existing Envoy bootstrap under Arion, read the refusals it produces, and reconnect to your xDS control plane."
---

> Arion site index: [/llms.txt](/llms.txt)

# Migrate from Envoy

Run an existing Envoy bootstrap under Arion, read the refusals it produces, and reconnect to your xDS control plane.

## Outcome

This guide takes a configuration written for Envoy and runs it under Arion — first as a static file, then attached to the xDS control plane you already operate. Use it when you have working Envoy config and want to move it, not rewrite it.

## The compatibility promise

If Arion accepts a configuration meant for Envoy, it behaves essentially like Envoy: filters run in the expected order and requests are routed or rejected as Envoy would. If Arion cannot honour part of a config the same way, it refuses to start when loading from a file, or rejects the update when it arrives over xDS — it never silently ignores what you wrote. The reasoning behind this is laid out in [Envoy compatibility](@/docs/concepts/envoy-compatibility.md).

## Prerequisites

- Your Envoy bootstrap YAML.
- An `arion` binary — see [install](@/docs/get-started/install.md).

## Run your bootstrap under Arion

There are two ways in, and neither changes the Envoy dialect itself.

The first is to wrap the bootstrap in an Arion file: everything that was top-level in Envoy moves under an `envoy_bootstrap:` key, leaving room for Arion-native keys such as `logging` beside it. A complete minimal example:

```yaml,name=arion.yaml,arion=full
envoy_bootstrap:
  static_resources:
    listeners:
      - name: http_8080
        address:
          socket_address: { address: 0.0.0.0, port_value: 8080 }
        filter_chains:
          - name: http_8080_chain
            filters:
              - name: http_gateway
                typed_config:
                  "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
                  http_filters:
                    - name: envoy.filters.http.router
                      typed_config:
                        "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
                  route_config:
                    name: http_8080_routes
                    virtual_hosts:
                      - name: backend
                        domains: ["*"]
                        routes:
                          - match:
                              prefix: /
                            route:
                              cluster: backend
    clusters:
      - name: backend
        connect_timeout: 5s
        type: STATIC
        lb_policy: ROUND_ROBIN
        load_assignment:
          cluster_name: backend
          endpoints:
            - lb_endpoints:
                - endpoint:
                    address:
                      socket_address: { address: 127.0.0.1, port_value: 5678 }
```

```bash
arion --config arion.yaml
```

The second way skips the wrapper entirely: point Arion straight at the unmodified Envoy file with `--with-envoy-bootstrap`.

```bash
arion --with-envoy-bootstrap envoy.yaml
```

The two flags combine — `--config` supplies Arion-native settings while `--with-envoy-bootstrap` overrides whatever bootstrap the config file carried. Inside a config file the same exclusivity holds: a file sets `envoy_bootstrap` or Arion's native `bootstrap` key, never both.

Nothing inside the bootstrap is translated by hand. Filter names, `@type` URLs, route match shapes, cluster fields — everything keeps its Envoy spelling, which is what makes the move reversible while you evaluate. Two dialect notes from real migrations: Arion requires `name` on resources Envoy lets you leave anonymous, filter chains especially, so name them before converting; and the connection manager's `stat_prefix`, mandatory in Envoy, is accepted but ignored with a startup warning.

## Read the refusals

The first run of a real-world bootstrap usually fails, deliberately. Suppose a cluster named `web` carries `outlier_detection`. Arion exits with an error that names the field and where it sits:

```text
Error parsing field static_resources / clusters ["web"]
Unsupported field: outlier_detection
```

The reaction is always the same: delete the field or replace it with a supported equivalent, consult the [supported configuration reference](@/reference/envoy-compatibility/supported-configuration.md) for what the surrounding resource accepts, and run again until Arion starts. An unsupported filter or extension fails as `Unsupported enum variant` naming its `@type` URL; a key Envoy itself does not know fails earlier, as a YAML error naming the position. Each refusal is a statement that Arion could not have honoured that line the way Envoy does — not a parser bug to work around.

Once it starts, treat that as the halfway point, not the finish. Run Arion beside the Envoy it replaces — on a different port or host — send the same requests through both, and compare status codes, routing decisions and access logs before cutting traffic over. Refusals catch what Arion will not do; a side-by-side soak is how you confirm what it does.

## Under your control plane

Dynamic configuration is the same story: Envoy's bootstrap shape, consumed unchanged, and to the control plane Arion looks like one more Envoy client identified by its `node.id` and `node.cluster`. Arion connects through `ads_config`, with a static cluster carrying the xDS server address:

```yaml,arion=fragment
envoy_bootstrap:
  node:
    id: arion-1
    cluster: edge
  dynamic_resources:
    ads_config:
      grpc_services:
        - envoy_grpc:
            cluster_name: xds_cluster
  static_resources:
    clusters:
      - name: xds_cluster
        connect_timeout: 0.25s
        type: STATIC
        typed_extension_protocol_options:
          envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
            "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
            explicit_http_config:
              http2_protocol_options: {}
        load_assignment:
          cluster_name: xds_cluster
          endpoints:
            - lb_endpoints:
                - endpoint:
                    address:
                      socket_address: { address: 127.0.0.1, port_value: 50051 }
```

Arion speaks the incremental (delta) variant of ADS, so the control plane must serve delta streams. Over that stream it subscribes to listeners, clusters, routes, endpoints and secrets — LDS, CDS, RDS, EDS and SDS — so a listener delivered dynamically may use `rds` with `config_source: { ads: {} }` and a `route_config_name` exactly as under Envoy. Everything must flow through ADS: separate `lds_config` or `cds_config` sources are refused at startup. When the control plane pushes a resource Arion cannot honour, the compatibility promise switches from refusing to start to rejecting the update: Arion NACKs, carrying the same error text back over the stream for the control plane to log, and the invalid resource is not applied. Setup detail is in [xDS control planes](@/docs/operate/xds-control-planes.md).

## Commonly hit unsupported areas

Fields that make working Envoy configs fail their first Arion run, all refused at load rather than ignored:

- `ext_authz`, as an HTTP or network filter — no external authorization callout exists.
- Cluster `outlier_detection` — active health checks are the ejection mechanism.
- Cluster `type: LOGICAL_DNS` — use `STRICT_DNS` or `STATIC`.
- Access log `json_format` — write a `text_format` whose template is a JSON object instead.
- Bootstrap-level `layered_runtime`, `overload_manager`, `stats_config`, `watchdog`, `cluster_manager` and `hds_config`.
- `lds_config` / `cds_config` in `dynamic_resources` — ADS only.

The list is not exhaustive; the [supported configuration reference](@/reference/envoy-compatibility/supported-configuration.md) is the authoritative inventory.

## Next

Read [Envoy compatibility](@/docs/concepts/envoy-compatibility.md) for what the guarantee promises and why, then move real traffic with the [L7 load balancer](@/guides/proxy/l7-load-balancer.md) pattern under Arion.
