Docs
Apache APISIXManage traffic and APIsService DiscoveryIntegrate with Netflix Eureka

Integrate with Netflix Eureka

Configure Apache APISIX to discover two controlled local services through Netflix Eureka, validate routing, and restore the original configuration.

Applies to:Apache APISIX 3.18.0

Deployments:DockerRoles:Platform engineerTime:25 minutesImpact:Changes live traffic

Goal

Register two disposable web services in Eureka and configure APISIX to discover them dynamically. The procedure isolates every container on the APISIX quickstart network, validates both endpoints, and restores the exact pre-guide APISIX configuration.

Before you begin

  • Complete Get APISIX with the Docker quickstart.
  • Install Docker, cURL, and jq.
  • Use a shell that supports Bash or Zsh heredocs.
  • The community bitinit/eureka image is suitable only for this isolated local evaluation. Use an approved, maintained Eureka deployment with authentication and TLS in production.

Set the endpoints and read the Admin API key without placing it in shell history:

export APISIX_ADMIN_URL="http://127.0.0.1:9180"
export GATEWAY_URL="http://127.0.0.1:9080"
export GUIDE_DIR="$(pwd)"
read -rsp "Admin API key (press Enter for an unchanged quickstart): " APISIX_ADMIN_KEY && export APISIX_ADMIN_KEY && echo

Protect the fixed Route, containers, and local files used by this guide:

test "$(curl -sS --connect-timeout 3 --max-time 10 -o /dev/null -w '%{http_code}' "${APISIX_ADMIN_URL}/apisix/admin/routes/eureka-guide-route" -H "X-API-KEY: ${APISIX_ADMIN_KEY}")" = 404 || { echo 'Stop: route eureka-guide-route already exists'; exit 1; }
for container in eureka-guide-registry eureka-guide-web-1 eureka-guide-web-2; do
  test -z "$(docker ps -aq --filter "name=^/${container}$")" || { echo "Stop: container ${container} already exists"; exit 1; }
done
for file in eureka-guide-nginx-1.conf eureka-guide-nginx-2.conf eureka-guide-config.yaml.backup eureka-guide-config.yaml.updated eureka-guide-responses.txt; do
  test ! -e "${file}" || { echo "Stop: ${file} already exists"; exit 1; }
done

Steps

Step 1: Start a controlled Eureka registry

Start a single-node Eureka server on the quickstart network without publishing its port:

docker run -d --name eureka-guide-registry \
  --network apisix-quickstart-net \
  -e ENVIRONMENT=apisix \
  -e spring.application.name=apisix-eureka \
  -e server.port=8761 \
  -e eureka.instance.ip-address=127.0.0.1 \
  -e eureka.client.registerWithEureka=true \
  -e eureka.client.fetchRegistry=false \
  -e eureka.client.serviceUrl.defaultZone=http://127.0.0.1:8761/eureka/ \
  bitinit/eureka:latest
for attempt in {1..60}; do
  docker run --rm --network apisix-quickstart-net curlimages/curl:8.16.0 \
    --fail --silent --show-error --connect-timeout 2 --max-time 5 \
    -H 'Accept: application/json' http://eureka-guide-registry:8761/eureka/apps >/dev/null && break
  sleep 1
done
docker run --rm --network apisix-quickstart-net curlimages/curl:8.16.0 \
  --fail --silent --show-error --connect-timeout 2 --max-time 5 \
  -H 'Accept: application/json' http://eureka-guide-registry:8761/eureka/apps >/dev/null

What changed: a disposable Eureka server is ready only on apisix-quickstart-net.

Step 2: Start and register two web services

Create two minimal NGINX configurations and start the services without publishing their ports:

cat > eureka-guide-nginx-1.conf <<'NGINX'
events {}
http {
  access_log off;
  server {
    listen 80;
    location = / { return 200 "eureka-guide-web-1"; }
  }
}
NGINX
cat > eureka-guide-nginx-2.conf <<'NGINX'
events {}
http {
  access_log off;
  server {
    listen 80;
    location = / { return 200 "eureka-guide-web-2"; }
  }
}
NGINX
docker run -d --name eureka-guide-web-1 --network apisix-quickstart-net \
  -v "${GUIDE_DIR}/eureka-guide-nginx-1.conf:/etc/nginx/nginx.conf:ro" nginx:1.29.1-alpine
docker run -d --name eureka-guide-web-2 --network apisix-quickstart-net \
  -v "${GUIDE_DIR}/eureka-guide-nginx-2.conf:/etc/nginx/nginx.conf:ro" nginx:1.29.1-alpine
for container in eureka-guide-web-1 eureka-guide-web-2; do
  for attempt in {1..15}; do
    docker exec apisix-quickstart bash -c "</dev/tcp/${container}/80" >/dev/null 2>&1 && break
    sleep 1
  done
  docker exec apisix-quickstart bash -c "</dev/tcp/${container}/80"
done

Capture each private address and register two UP instances under the same application name:

for number in 1 2; do
  jq -n --arg id "eureka-guide-web-${number}" \
    --arg ip "$(docker inspect "eureka-guide-web-${number}" | jq -r '.[0].NetworkSettings.Networks["apisix-quickstart-net"].IPAddress')" \
    '{instance:{instanceId:$id,hostName:$id,ipAddr:$ip,port:{"$":80,"@enabled":true},status:"UP",app:"EUREKA-GUIDE-WEB",dataCenterInfo:{name:"MyOwn","@class":"com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo"}}}' | \
  docker run --rm -i --network apisix-quickstart-net curlimages/curl:8.16.0 \
    --fail --silent --show-error --connect-timeout 2 --max-time 10 \
    -X POST http://eureka-guide-registry:8761/eureka/apps/EUREKA-GUIDE-WEB \
    -H 'Content-Type: application/json' --data-binary @-
done
docker run --rm --network apisix-quickstart-net curlimages/curl:8.16.0 \
  --fail --silent --show-error --connect-timeout 2 --max-time 10 \
  -H 'Accept: application/json' http://eureka-guide-registry:8761/eureka/apps/EUREKA-GUIDE-WEB | \
  jq -e '([.application.instance[].instanceId] | sort) == ["eureka-guide-web-1","eureka-guide-web-2"] and all(.application.instance[]; .status == "UP")'

What changed: Eureka resolves EUREKA-GUIDE-WEB to exactly the two private container addresses.

Step 3: Enable Eureka discovery in APISIX

Back up the complete APISIX configuration. Stop if it already contains a top-level discovery block, because that configuration requires a reviewed merge:

umask 077
docker cp apisix-quickstart:/usr/local/apisix/conf/config.yaml eureka-guide-config.yaml.backup
test "$(grep -Ec '^discovery:[[:space:]]*$' eureka-guide-config.yaml.backup)" = 0 || { echo 'Stop: merge with the existing top-level discovery configuration'; exit 1; }
cp eureka-guide-config.yaml.backup eureka-guide-config.yaml.updated
cat >> eureka-guide-config.yaml.updated <<'YAML'

discovery:
  eureka:
    host:
      - "http://eureka-guide-registry:8761"
    prefix: /eureka/
    fetch_interval: 5
    weight: 100
    timeout:
      connect: 2000
      send: 2000
      read: 5000
YAML
docker cp eureka-guide-config.yaml.updated apisix-quickstart:/usr/local/apisix/conf/config.yaml
if ! docker exec apisix-quickstart apisix test; then
  docker cp eureka-guide-config.yaml.backup apisix-quickstart:/usr/local/apisix/conf/config.yaml
  docker exec apisix-quickstart apisix reload
  exit 1
fi
docker exec apisix-quickstart apisix reload

What changed: APISIX uses the local Eureka endpoint while retaining every pre-existing configuration field.

Step 4: Create the discovery Route

Create one authenticated fixed-ID Route and assert the stored discovery settings:

curl -sS --connect-timeout 3 --max-time 15 \
  "${APISIX_ADMIN_URL}/apisix/admin/routes/eureka-guide-route" -X PUT \
  -H "X-API-KEY: ${APISIX_ADMIN_KEY}" -H 'Content-Type: application/json' \
  -d '{
    "name":"eureka-guide-route",
    "uri":"/eureka-guide",
    "methods":["GET"],
    "upstream":{
      "service_name":"EUREKA-GUIDE-WEB",
      "discovery_type":"eureka",
      "type":"roundrobin"
    }
  }' | jq -e '.value.id == "eureka-guide-route" and .value.upstream.service_name == "EUREKA-GUIDE-WEB" and .value.upstream.discovery_type == "eureka"'

What changed: GET /eureka-guide selects nodes discovered from the controlled Eureka application.

Validate

Allow discovery to converge, then require successful responses from both registered endpoints:

for attempt in {1..30}; do
  : > eureka-guide-responses.txt
  for request in {1..8}; do
    curl --fail-with-body -sS --connect-timeout 3 --max-time 10 "${GATEWAY_URL}/eureka-guide"
    echo
  done > eureka-guide-responses.txt
  test "$(sort -u eureka-guide-responses.txt | tr '\n' ' ')" = 'eureka-guide-web-1 eureka-guide-web-2 ' && break
  sleep 1
done
test "$(sort -u eureka-guide-responses.txt | tr '\n' ' ')" = 'eureka-guide-web-1 eureka-guide-web-2 '

The final assertion proves that APISIX reached both nodes returned for the same Eureka application.

Roll back / Clean up

Delete the Route, deregister the instances, restore the exact APISIX configuration, and remove all disposable resources:

curl --fail-with-body -sS --connect-timeout 3 --max-time 15 -X DELETE \
  "${APISIX_ADMIN_URL}/apisix/admin/routes/eureka-guide-route" \
  -H "X-API-KEY: ${APISIX_ADMIN_KEY}"
for number in 1 2; do
  docker run --rm --network apisix-quickstart-net curlimages/curl:8.16.0 \
    --fail --silent --show-error --connect-timeout 2 --max-time 10 \
    -X DELETE "http://eureka-guide-registry:8761/eureka/apps/EUREKA-GUIDE-WEB/eureka-guide-web-${number}"
done
docker cp eureka-guide-config.yaml.backup apisix-quickstart:/usr/local/apisix/conf/config.yaml
docker exec apisix-quickstart apisix test
docker exec apisix-quickstart apisix reload
docker rm -f eureka-guide-web-1 eureka-guide-web-2 eureka-guide-registry
rm -f eureka-guide-nginx-1.conf eureka-guide-nginx-2.conf eureka-guide-config.yaml.backup \
  eureka-guide-config.yaml.updated eureka-guide-responses.txt
unset APISIX_ADMIN_URL APISIX_ADMIN_KEY GATEWAY_URL GUIDE_DIR

Expected: the fixed Route, two registrations, three containers, and local files are absent, and APISIX is running with the byte-for-byte pre-guide configuration.

Troubleshoot

SymptomCauseFix
The configuration guard stops at discovery.APISIX already has service discovery configuration.Do not append another top-level key. Merge eureka into the existing discovery mapping in a reviewed change and retain the exact backup.
apisix test fails.The updated YAML is invalid or incompatible with the running APISIX image.Keep the automatic restoration, inspect the test output, and do not reload the rejected file.
The application lookup returns a non-JSON response.Eureka is still starting or the request did not ask for JSON.Re-run the readiness request with Accept: application/json before registering instances.
The Route returns 503 or 502.Discovery has not converged, a registration is absent, or a private container address changed.Re-register both current addresses and verify that all containers share apisix-quickstart-net.
Validation sees only one response.One NGINX container is unavailable or Eureka exposes only one UP instance.Check both TCP readiness tests and the exact instance assertion before increasing the retry loop.