hamidou-ie / symfony-eureka
Symfony bundle to register/heartbeat services into Netflix Eureka
Installs: 11
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 0
Forks: 0
Open Issues: 0
Type:symfony-bundle
pkg:composer/hamidou-ie/symfony-eureka
Requires
- php: >=8.2
- psr/log: ^2.0 || ^3.0
- symfony/config: ^6.4 || ^7.0 || ^8.0
- symfony/console: ^6.4 || ^7.0 || ^8.0
- symfony/dependency-injection: ^6.4 || ^7.0 || ^8.0
- symfony/framework-bundle: ^6.4 || ^7.0 || ^8.0
- symfony/http-client: ^6.4 || ^7.0 || ^8.0
- symfony/http-kernel: ^6.4 || ^7.0 || ^8.0
Requires (Dev)
- phpunit/phpunit: ^11.0
This package is auto-updated.
Last update: 2026-02-24 11:14:56 UTC
README
Eureka (Spring Cloud Netflix) client packaged as a Symfony Bundle.
This bundle lets you:
- register your service instance in Eureka (
register) - send a one-shot heartbeat (
heartbeat) - deregister your instance (
deregister) - do service discovery (
fetchInstance,fetchInstances) - customize how an instance is picked via a discovery strategy (default: random)
- use a fallback local registry/cache via an
InstanceProviderwhen Eureka is unavailable
Note: this package provides a Eureka client.
Requirements
- PHP
>= 8.2 - Symfony
^6.4 || ^7.0 || ^8.0 - A running Eureka server (e.g. Spring Cloud Netflix Eureka)
HTTP transport is provided by symfony/http-client.
Installation
composer require hamidou-ie/symfony-eureka
Enable the bundle
If Symfony Flex does not enable it automatically, add it to config/bundles.php:
<?php return [ // ... HamidouIe\SymfonyEureka\EurekaBundle::class => ['all' => true], ];
Configuration
You can configure the bundle:
- via environment variables (
.env, OS env, secrets) - via
config/packages/eureka.yaml(which can override env)
Minimal .env example
EUREKA_ENABLED=1 EUREKA_SERVER_URL=http://localhost:8761/eureka EUREKA_APP_NAME=my-service EUREKA_INSTANCE_HOSTNAME=localhost EUREKA_INSTANCE_IP=127.0.0.1 EUREKA_INSTANCE_PORT=8000 EUREKA_PREFER_IP=0 EUREKA_LEASE_RENEWAL_INTERVAL=30 EUREKA_LEASE_EXPIRATION=90
Full .env example
# toggle EUREKA_ENABLED=1 # Eureka base URL EUREKA_SERVER_URL=http://localhost:8761/eureka # app name (will be uppercased in the payload) EUREKA_APP_NAME=my-service # instance identity EUREKA_INSTANCE_HOSTNAME=my-service.local EUREKA_INSTANCE_IP=127.0.0.1 EUREKA_INSTANCE_PORT=8000 EUREKA_PREFER_IP=0 # optional: explicit instance id; otherwise it is computed EUREKA_INSTANCE_ID= EUREKA_INSTANCE_SUFFIX= # status EUREKA_STATUS=UP EUREKA_OVERRIDDEN_STATUS=UNKNOWN # public URLs (optional) # If not set, defaults are computed as: # - homePageUrl: http://{host}:{port}/ # - statusPageUrl: http://{host}:{port}/info # - healthCheckUrl: http://{host}:{port}/health EUREKA_HOME_PAGE_URL= EUREKA_STATUS_PAGE_URL= EUREKA_HEALTH_CHECK_URL= # VIP (optional) - if empty, defaults to strtolower(appName) EUREKA_VIP_ADDRESS= EUREKA_SECURE_VIP_ADDRESS= # secure port (optional) EUREKA_SECURE_PORT=443 EUREKA_SECURE_PORT_ENABLED=0 # country (optional) EUREKA_COUNTRY_ID=1 # lease/heartbeat EUREKA_LEASE_RENEWAL_INTERVAL=30 EUREKA_LEASE_EXPIRATION=90 # Basic auth (if your Eureka is protected) EUREKA_BASIC_USER= EUREKA_BASIC_PASSWORD=
Running multiple instances of the same app
Eureka identifies instances by app + instanceId. If you run multiple instances, make sure each instance has a unique instanceId.
This bundle computes instanceId like this when EUREKA_INSTANCE_ID is empty:
{hostname-or-ip}:{appName}:{suffix}
suffixdefaults toEUREKA_INSTANCE_PORThostname-or-ipdepends onEUREKA_PREFER_IP
So, running multiple instances is already fine if each instance uses a different port.
If you run multiple instances on the same host and same port (rare, but can happen behind proxies), set one of:
EUREKA_INSTANCE_SUFFIX(example:blue,canary,node-1)- or
EUREKA_INSTANCE_ID(explicit unique value)
Example config/packages/eureka.yaml
eureka: enabled: '%env(bool:EUREKA_ENABLED)%' server_url: '%env(EUREKA_SERVER_URL)%' app_name: '%env(EUREKA_APP_NAME)%' instance: hostname: '%env(EUREKA_INSTANCE_HOSTNAME)%' ip: '%env(EUREKA_INSTANCE_IP)%' port: '%env(int:EUREKA_INSTANCE_PORT)%' prefer_ip: '%env(bool:EUREKA_PREFER_IP)%' id: '%env(default::EUREKA_INSTANCE_ID)%' suffix: '%env(default::EUREKA_INSTANCE_SUFFIX)%' status: '%env(EUREKA_STATUS)%' overridden_status: '%env(EUREKA_OVERRIDDEN_STATUS)%' home_page_url: '%env(default::EUREKA_HOME_PAGE_URL)%' status_page_url: '%env(default::EUREKA_STATUS_PAGE_URL)%' health_check_url: '%env(default::EUREKA_HEALTH_CHECK_URL)%' vip_address: '%env(default::EUREKA_VIP_ADDRESS)%' secure_vip_address: '%env(default::EUREKA_SECURE_VIP_ADDRESS)%' secure_port: port: '%env(int:EUREKA_SECURE_PORT)%' enabled: '%env(bool:EUREKA_SECURE_PORT_ENABLED)%' country_id: '%env(int:EUREKA_COUNTRY_ID)%' lease: renewal_interval: '%env(int:EUREKA_LEASE_RENEWAL_INTERVAL)%' expiration: '%env(int:EUREKA_LEASE_EXPIRATION)%' basic_auth: user: '%env(default::EUREKA_BASIC_USER)%' password: '%env(default::EUREKA_BASIC_PASSWORD)%'
Symfony Console commands
The bundle provides 3 commands:
app:eureka:register— register this instanceapp:eureka:heartbeat— send a one-shot heartbeatapp:eureka:deregister— deregister this instance
Examples:
php bin/console app:eureka:register php bin/console app:eureka:heartbeat php bin/console app:eureka:deregister
If
EUREKA_ENABLED=0, these commands will skip the HTTP calls.
Using it in code
Inject HamidouIe\SymfonyEureka\Service\EurekaClient and call its methods.
use HamidouIe\SymfonyEureka\Service\EurekaClient; final class MyService { public function __construct(private EurekaClient $eureka) {} public function boot(): void { $this->eureka->register(); // then schedule periodic heartbeats (see “Keep the instance UP”) } }
Check whether the instance is registered
if (!$eureka->isRegistered()) { $eureka->register(); }
Service discovery
Fetch all instances for a service
$instances = $eureka->fetchInstances('the-service');
This method:
- calls
GET {EUREKA_SERVER_URL}/apps/{APPNAME} - parses the JSON payload
- returns a list of
HamidouIe\SymfonyEureka\Dto\EurekaInstance - filters out instances whose
statusis notUP
Pick a single instance (load-balancing)
$instance = $eureka->fetchInstance('the-service'); if ($instance !== null) { $baseUrl = $instance->homePageUrl; }
Discovery strategy (instance selection)
By default, the bundle uses a random strategy: RandomStrategy.
To customize instance selection, implement:
HamidouIe\SymfonyEureka\Discovery\DiscoveryStrategyInterface
Example (simple round-robin, adapt to your needs):
namespace App\Eureka; use HamidouIe\SymfonyEureka\Discovery\DiscoveryStrategyInterface; use HamidouIe\SymfonyEureka\Dto\EurekaInstance; final class RoundRobinStrategy implements DiscoveryStrategyInterface { private int $idx = 0; public function pickInstance(array $instances): ?EurekaInstance { if ($instances === []) { return null; } $i = $this->idx % count($instances); $this->idx++; return $instances[$i]; } }
Then alias the interface in config/services.yaml:
services: App\Eureka\RoundRobinStrategy: ~ HamidouIe\SymfonyEureka\Discovery\DiscoveryStrategyInterface: alias: App\Eureka\RoundRobinStrategy
Local registry / caching (fallback when Eureka is down)
When Eureka is unreachable or returns an unexpected payload, the bundle can ask a local provider for instances.
Implement:
HamidouIe\SymfonyEureka\Discovery\InstanceProviderInterface
Example (pseudo-cache):
namespace App\Eureka; use HamidouIe\SymfonyEureka\Discovery\InstanceProviderInterface; use HamidouIe\SymfonyEureka\Dto\EurekaInstance; final class RedisInstanceProvider implements InstanceProviderInterface { public function getInstances(string $appName): array { // load cached instances from Redis / DB / file… return [ new EurekaInstance( instanceId: null, hostName: 'fallback.local', ipAddr: '127.0.0.1', port: 8080, portEnabled: true, homePageUrl: 'http://fallback.local:8080/', statusPageUrl: null, healthCheckUrl: null, status: 'UP', ), ]; } }
Register it (the interface will be injected if it exists):
services: App\Eureka\RedisInstanceProvider: ~ HamidouIe\SymfonyEureka\Discovery\InstanceProviderInterface: alias: App\Eureka\RedisInstanceProvider
Keep the instance “UP” (periodic heartbeats)
The bundle provides a one-shot heartbeat() and the app:eureka:heartbeat command, but it does not (yet) provide a long-running start() loop.
Practical options:
Option A — Cron (simple)
- call
php bin/console app:eureka:heartbeateveryEUREKA_LEASE_RENEWAL_INTERVALseconds
Option B — Process manager (prod)
- supervisor/systemd/k8s: run a worker/command that heartbeats in a loop
Option C — Dev quick loop (PowerShell)
while ($true) { php bin/console app:eureka:heartbeat --env=dev; Start-Sleep -Seconds 30 }
Troubleshooting
My service does not appear in Eureka
- Check
EUREKA_ENABLED=1 - Check
EUREKA_SERVER_URL(ensure the trailing/eurekais correct for your server) - Ensure
EUREKA_INSTANCE_HOSTNAME/EUREKA_INSTANCE_IP/EUREKA_INSTANCE_PORTare reachable from the Eureka server
Heartbeat returns 404
This usually means the instance is not registered (or the instanceId does not match).
- run
app:eureka:register - verify your instance id settings (
EUREKA_INSTANCE_ID/EUREKA_INSTANCE_SUFFIX)
Eureka is protected (Basic Auth)
- set
EUREKA_BASIC_USER/EUREKA_BASIC_PASSWORD
License
MIT