mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-28 22:35:42 +00:00
Fix Docker deployment connectivity issues (#4795)
* Fix Docker deployment connectivity issues - Shardok: Listen on 0.0.0.0:40042 instead of localhost for container networking - Shardok: Add env var fallback for resource paths (SHARDOK_RESOURCES_PATH, SHARDOK_MAPS_PATH) - Eagle: Use plaintext gRPC for internal container-to-container communication - docker-compose: Pass CLI args directly instead of env vars, default to gpt-5.1 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Document Docker networking and resource configuration Added section explaining: - Why Shardok binds to 0.0.0.0 instead of localhost for container networking - Why Eagle uses .usePlaintext() for internal gRPC - How env var fallbacks replace Bazel runfiles in Docker - Future considerations for multi-host deployment 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,15 +11,15 @@ services:
|
||||
eagle:
|
||||
image: ${EAGLE_IMAGE:-registry.digitalocean.com/eagle0/eagle-server:latest}
|
||||
container_name: eagle-server
|
||||
command:
|
||||
- "--gpt-model-name"
|
||||
- "${GPT_MODEL_NAME:-gpt-5.1}"
|
||||
- "--shardok-interface-remote-address"
|
||||
- "shardok:40042"
|
||||
ports:
|
||||
- "40032:40032"
|
||||
environment:
|
||||
EAGLE_GRPC_PORT: "40032"
|
||||
SHARDOK_HOST: "shardok"
|
||||
SHARDOK_PORT: "40042"
|
||||
EAGLE_RESOURCES_PATH: "/app/resources"
|
||||
OPENAI_API_KEY: "${OPENAI_API_KEY:-}"
|
||||
GPT_MODEL_NAME: "${GPT_MODEL_NAME:-gpt-4o}"
|
||||
volumes:
|
||||
- ./saves:/app/saves
|
||||
depends_on:
|
||||
|
||||
@@ -348,6 +348,68 @@ services:
|
||||
|
||||
---
|
||||
|
||||
## Docker Networking & Resource Configuration
|
||||
|
||||
### Container Network Binding
|
||||
|
||||
In Docker, each container has its own network namespace. When a server binds to `localhost` (127.0.0.1), it only accepts connections from within the same container. Other containers on the Docker bridge network cannot connect, even if they can resolve the hostname.
|
||||
|
||||
**Shardok must bind to `0.0.0.0:40042`** to accept connections from Eagle running in a separate container. This is configured in `ServerConfiguration.cpp`:
|
||||
|
||||
```cpp
|
||||
// Default to 0.0.0.0 for container networking (accepts connections from any interface)
|
||||
{ServerConfiguration::kEagleInterfaceGrpcAddress, "0.0.0.0:40042"}
|
||||
```
|
||||
|
||||
This still works on the local Mac because `0.0.0.0` means "all interfaces", which includes the loopback interface that localhost uses.
|
||||
|
||||
### gRPC Plaintext for Internal Communication
|
||||
|
||||
By default, gRPC's `ManagedChannelBuilder` attempts TLS connections. Since Shardok runs in plaintext mode, Eagle must use `.usePlaintext()` for internal container-to-container communication:
|
||||
|
||||
```scala
|
||||
// ServerSetupHelpers.scala
|
||||
val channelBuilder = ManagedChannelBuilder
|
||||
.forAddress(shardokInterfaceAddress, shardokInterfacePort)
|
||||
.usePlaintext() // Required for internal Docker communication
|
||||
```
|
||||
|
||||
TLS termination happens at nginx for external clients. Internal traffic between Eagle and Shardok stays within the Docker network and doesn't need encryption.
|
||||
|
||||
**Future consideration:** If Shardok moves to a separate droplet, traffic would traverse the network. Options:
|
||||
1. Enable TLS between Eagle and Shardok (configure Shardok with certificates)
|
||||
2. Use DigitalOcean VPC (private network, still unencrypted but isolated)
|
||||
3. Use WireGuard/VPN tunnel between hosts
|
||||
|
||||
### Bazel Runfiles in Docker
|
||||
|
||||
Bazel's runfiles system locates resources relative to the executable using a manifest or directory structure that Bazel sets up at runtime. This doesn't exist in Docker containers.
|
||||
|
||||
**Solution:** Environment variable fallbacks in `FilesystemUtils.cpp`:
|
||||
|
||||
```cpp
|
||||
// Check for Docker deployment env vars first
|
||||
const char* resourcesPath = getenv("SHARDOK_RESOURCES_PATH");
|
||||
if (resourcesPath != nullptr) {
|
||||
return string(resourcesPath) + "/";
|
||||
}
|
||||
// Fall back to Bazel runfiles for local development
|
||||
return rLocation + "/src/main/resources/net/eagle0/shardok/";
|
||||
```
|
||||
|
||||
The Docker Compose configuration sets these:
|
||||
```yaml
|
||||
environment:
|
||||
SHARDOK_RESOURCES_PATH: "/app/resources"
|
||||
SHARDOK_MAPS_PATH: "/app/resources/maps"
|
||||
```
|
||||
|
||||
This allows the same binary to work in both environments:
|
||||
- **Local dev:** No env var → uses Bazel runfiles
|
||||
- **Docker:** Env var set → uses `/app/resources/` directly
|
||||
|
||||
---
|
||||
|
||||
## Build Pipeline
|
||||
|
||||
### Build Artifacts
|
||||
|
||||
@@ -26,6 +26,13 @@ namespace fs = std::filesystem;
|
||||
static string rLocation;
|
||||
|
||||
auto rloc(const string& execPath) -> string {
|
||||
// First check for environment variable override for Docker deployment
|
||||
const char* resourcesPath = getenv("SHARDOK_RESOURCES_PATH");
|
||||
if (resourcesPath != nullptr) {
|
||||
return ""; // Return empty so StaticShardokFilesDirectory uses env var directly
|
||||
}
|
||||
|
||||
// Fall back to Bazel runfiles for development
|
||||
string error;
|
||||
const std::unique_ptr<Runfiles> runfiles(Runfiles::Create(execPath, &error));
|
||||
|
||||
@@ -58,10 +65,14 @@ auto FilesystemUtils::FileExistsAtPath(const string& path) -> bool { return fs::
|
||||
auto FilesystemUtils::StaticEagle0FilesDirectory() -> string { return "/usr/local/share/eagle0/"; }
|
||||
|
||||
auto FilesystemUtils::StaticShardokFilesDirectory() -> string {
|
||||
const char* resourcesPath = getenv("SHARDOK_RESOURCES_PATH");
|
||||
if (resourcesPath != nullptr) { return string(resourcesPath) + "/"; }
|
||||
return rLocation + "/src/main/resources/net/eagle0/shardok/";
|
||||
}
|
||||
|
||||
auto FilesystemUtils::MapFilesDirectory() -> string {
|
||||
const char* mapsPath = getenv("SHARDOK_MAPS_PATH");
|
||||
if (mapsPath != nullptr) { return string(mapsPath) + "/"; }
|
||||
return StaticShardokFilesDirectory() + "maps/";
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "ServerConfiguration.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
@@ -30,9 +31,14 @@ using std::unordered_map;
|
||||
unordered_map<string, string> DefaultValues();
|
||||
|
||||
unordered_map<string, string> DefaultValues() {
|
||||
// Check environment variables for Docker deployment
|
||||
const char* eagleInterfaceAddr = getenv("SHARDOK_EAGLE_INTERFACE_ADDRESS");
|
||||
const char* shardokAddr = getenv("SHARDOK_GRPC_ADDRESS");
|
||||
|
||||
return unordered_map<string, string>{
|
||||
{ServerConfiguration::kShardokGrpcAddress, "localhost"},
|
||||
{ServerConfiguration::kEagleInterfaceGrpcAddress, "localhost"}};
|
||||
{ServerConfiguration::kShardokGrpcAddress, shardokAddr ? shardokAddr : "localhost"},
|
||||
{ServerConfiguration::kEagleInterfaceGrpcAddress,
|
||||
eagleInterfaceAddr ? eagleInterfaceAddr : "0.0.0.0:40042"}};
|
||||
}
|
||||
|
||||
ServerConfiguration::ServerConfiguration(const string& filePath) {
|
||||
|
||||
@@ -93,6 +93,7 @@ object ServerSetupHelpers {
|
||||
): Channel = {
|
||||
val channelBuilder = ManagedChannelBuilder
|
||||
.forAddress(shardokInterfaceAddress, shardokInterfacePort)
|
||||
.usePlaintext()
|
||||
|
||||
channelBuilder.build
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user