Improve development environment with Docker Compose profiles (#435)

* Add improved development environment with backend in Docker and frontend locally

- Created dev.bat script to run backend services in Docker and frontend locally
- Added docker-compose.backend.yml for backend-only Docker setup
- Updated package.json to run frontend on port 3737
- Fixed api.ts to use default port 8181 instead of throwing error
- Script automatically stops production containers to avoid port conflicts
- Provides instant HMR for frontend development

* Refactor development environment setup: replace dev.bat with Makefile for cross-platform support and enhanced commands

* Enhance development environment: add environment variable checks and update test commands for frontend and backend

* Improve development environment with Docker Compose profiles

This commit enhances the development workflow by replacing the separate
docker-compose.backend.yml file with Docker Compose profiles, fixing
critical service discovery issues, and adding comprehensive developer
tooling through an improved Makefile system.

Key improvements:
- Replace docker-compose.backend.yml with cleaner profile approach
- Fix service discovery by maintaining consistent container names
- Fix port mappings (3737:3737 instead of 3737:5173)
- Add make doctor for environment validation
- Fix port configuration and frontend HMR
- Improve error handling with .SHELLFLAGS in Makefile
- Add comprehensive port configuration via environment variables
- Simplify make dev-local to only run essential services
- Add logging directory creation for local development
- Document profile strategy in docker-compose.yml

These changes provide three flexible development modes:
- Hybrid mode (default): Backend in Docker, frontend local with HMR
- Docker mode: Everything in Docker for production-like testing
- Local mode: API server and UI run locally

Co-authored-by: Zak Stam <zaksnet@users.noreply.github.com>

* Fix make stop command to properly handle Docker Compose profiles

The stop command now explicitly specifies all profiles to ensure
all containers are stopped regardless of how they were started.

* Fix README to document correct make commands

- Changed 'make lint' to 'make lint-frontend' and 'make lint-backend'
- Removed non-existent 'make logs-server' command
- Added 'make watch-mcp' and 'make watch-agents' commands
- All documented make commands now match what's available in Makefile

* fix: Address critical issues from code review #435

- Create robust environment validation script (check-env.js) that properly parses .env files
- Fix Docker healthcheck port mismatch (5173 -> 3737)
- Remove hard-coded port flags from package.json to allow environment configuration
- Fix Docker detection logic using /.dockerenv instead of HOSTNAME
- Normalize container names to lowercase (archon-server, archon-mcp, etc.)
- Improve stop-local command with port-based fallback for process killing
- Fix API configuration fallback chain to include VITE_PORT
- Fix Makefile shell variable expansion using runtime evaluation
- Update .PHONY targets with comprehensive list
- Add --profile flags to Docker Compose commands in README
- Add VITE_ARCHON_SERVER_PORT to docker-compose.yml
- Add Node.js 18+ to prerequisites
- Use dynamic ports in Makefile help messages
- Add lint alias combining frontend and backend linting
- Update .env.example documentation
- Scope .gitignore logs entry to /logs/

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix container name resolution for MCP server

- Add dynamic container name resolution with three-tier strategy
- Support environment variables for custom container names
- Add service discovery labels to docker-compose services
- Update BackendStartupError with correct container name references

* Fix frontend test failures in API configuration tests

- Update environment variable names to use VITE_ prefix that matches production code
- Fix MCP client service tests to use singleton instance export
- Update default behavior tests to expect fallback to port 8181
- All 77 frontend tests now pass

* Fix make stop-local to avoid Docker daemon interference

Replace aggressive kill -9 with targeted process termination:
- Filter processes by command name (node/vite/python/uvicorn) before killing
- Use graceful SIGTERM instead of SIGKILL
- Add process verification to avoid killing Docker-related processes
- Improve logging with descriptive step messages

* refactor: Simplify development workflow based on comprehensive review

- Reduced Makefile from 344 lines (43 targets) to 83 lines (8 essential targets)
- Removed unnecessary environment variables (*_CONTAINER_NAME variables)
- Fixed Windows compatibility by removing Unix-specific commands
- Added security fixes to check-env.js (path validation)
- Simplified MCP container discovery to use fixed container names
- Fixed 'make stop' to properly handle Docker Compose profiles
- Updated documentation to reflect simplified workflow
- Restored original .env.example with comprehensive Supabase key documentation

This addresses all critical issues from code review:
- Cross-platform compatibility 
- Security vulnerabilities fixed 
- 81% reduction in complexity 
- Maintains all essential functionality 

All tests pass: Frontend (77/77), Backend (267/267)

* feat: Add granular test and lint commands to Makefile

- Split test command into test-fe and test-be for targeted testing
- Split lint command into lint-fe and lint-be for targeted linting
- Keep original test and lint commands that run both
- Update help text with new commands for better developer experience

* feat: Improve Docker Compose detection and prefer modern syntax

- Prefer 'docker compose' (plugin) over 'docker-compose' (standalone)
- Add better error handling in Makefile with proper exit on failures
- Add Node.js check before running environment scripts
- Pass environment variables correctly to frontend in hybrid mode
- Update all documentation to use modern 'docker compose' syntax
- Auto-detect which Docker Compose version is available

* docs: Update CONTRIBUTING.md to reflect simplified development workflow

- Add Node.js 18+ as prerequisite for hybrid development
- Mark Make as optional throughout the documentation
- Update all docker-compose commands to modern 'docker compose' syntax
- Add Make command alternatives for testing (make test, test-fe, test-be)
- Document make dev for hybrid development mode
- Remove linting requirements until codebase errors are resolved

* fix: Rename frontend service to archon-frontend for consistency

Aligns frontend service naming with other services (archon-server, archon-mcp, archon-agents) for better consistency in Docker image naming patterns.

---------

Co-authored-by: Zak Stam <zakscomputers@hotmail.com>
Co-authored-by: Zak Stam <zaksnet@users.noreply.github.com>
This commit is contained in:
Wirasm
2025-08-22 17:18:10 +03:00
committed by GitHub
parent cb4dba14a0
commit 86dd1b0749
12 changed files with 481 additions and 75 deletions

View File

@@ -18,8 +18,8 @@ RUN mkdir -p /app/coverage && chmod 777 /app/coverage
# Copy source code
COPY . .
# Expose Vite's default port
EXPOSE 5173
# Expose the port configured in package.json (3737)
EXPOSE 3737
# Start Vite dev server with host binding for Docker
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
# Start Vite dev server (already configured with --port 3737 --host in package.json)
CMD ["npm", "run", "dev"]

View File

@@ -28,12 +28,12 @@ export const BackendStartupError: React.FC = () => {
<span className="font-semibold">Check Docker Logs</span>
</div>
<p className="text-red-100 font-mono text-sm mb-3">
Check the <span className="text-red-400 font-bold">Archon-Server</span> logs in Docker Desktop for detailed error information.
Check the <span className="text-red-400 font-bold">Archon API server</span> container logs in Docker Desktop for detailed error information.
</p>
<div className="space-y-2 text-xs text-red-300">
<p>1. Open Docker Desktop</p>
<p>2. Go to Containers tab</p>
<p>3. Click on <span className="text-red-400 font-semibold">Archon-Server</span></p>
<p>3. Look for the Archon server container (typically named <span className="text-red-400 font-semibold">archon-server</span> or similar)</p>
<p>4. View the logs for the specific error message</p>
</div>
</div>

View File

@@ -20,14 +20,11 @@ export function getApiUrl(): string {
// For development, construct from window location
const protocol = window.location.protocol;
const host = window.location.hostname;
const port = import.meta.env.ARCHON_SERVER_PORT;
// Use configured port or default to 8181
const port = import.meta.env.VITE_ARCHON_SERVER_PORT || '8181';
if (!port) {
throw new Error(
'ARCHON_SERVER_PORT environment variable is required. ' +
'Please set it in your environment variables. ' +
'Default value: 8181'
);
if (!import.meta.env.VITE_ARCHON_SERVER_PORT) {
console.info('[Archon] Using default ARCHON_SERVER_PORT: 8181');
}
return `${protocol}//${host}:${port}`;

View File

@@ -41,23 +41,33 @@ describe('API Configuration', () => {
expect(getApiUrl()).toBe('');
});
it('should throw error when ARCHON_SERVER_PORT is not set in development', async () => {
// Development mode without port
it('should use default port 8181 when no port environment variables are set in development', async () => {
// Development mode without any port variables
delete (import.meta.env as any).PROD;
delete (import.meta.env as any).VITE_API_URL;
delete (import.meta.env as any).VITE_ARCHON_SERVER_PORT;
delete (import.meta.env as any).VITE_PORT;
delete (import.meta.env as any).ARCHON_SERVER_PORT;
// Mock window.location
Object.defineProperty(window, 'location', {
value: {
protocol: 'http:',
hostname: 'localhost'
},
writable: true
});
const { getApiUrl } = await import('../../src/config/api');
expect(() => getApiUrl()).toThrow('ARCHON_SERVER_PORT environment variable is required');
expect(() => getApiUrl()).toThrow('Default value: 8181');
expect(getApiUrl()).toBe('http://localhost:8181');
});
it('should use ARCHON_SERVER_PORT when set in development', async () => {
// Development mode with custom port
it('should use VITE_ARCHON_SERVER_PORT when set in development', async () => {
// Development mode with custom port via VITE_ prefix
delete (import.meta.env as any).PROD;
delete (import.meta.env as any).VITE_API_URL;
(import.meta.env as any).ARCHON_SERVER_PORT = '9191';
(import.meta.env as any).VITE_ARCHON_SERVER_PORT = '9191';
// Mock window.location
Object.defineProperty(window, 'location', {
@@ -73,10 +83,10 @@ describe('API Configuration', () => {
});
it('should use custom port with https protocol', async () => {
// Development mode with custom port and https
// Development mode with custom port and https via VITE_ prefix
delete (import.meta.env as any).PROD;
delete (import.meta.env as any).VITE_API_URL;
(import.meta.env as any).ARCHON_SERVER_PORT = '8443';
(import.meta.env as any).VITE_ARCHON_SERVER_PORT = '8443';
// Mock window.location with https
Object.defineProperty(window, 'location', {
@@ -139,7 +149,7 @@ describe('API Configuration', () => {
vi.resetModules();
delete (import.meta.env as any).PROD;
delete (import.meta.env as any).VITE_API_URL;
(import.meta.env as any).ARCHON_SERVER_PORT = port;
(import.meta.env as any).VITE_ARCHON_SERVER_PORT = port;
Object.defineProperty(window, 'location', {
value: {
@@ -174,11 +184,10 @@ describe('MCP Client Service Configuration', () => {
it('should throw error when ARCHON_MCP_PORT is not set', async () => {
delete (import.meta.env as any).ARCHON_MCP_PORT;
const { MCPClientService } = await import('../../src/services/mcpClientService');
const service = new MCPClientService();
const { mcpClientService } = await import('../../src/services/mcpClientService');
await expect(service.createArchonClient()).rejects.toThrow('ARCHON_MCP_PORT environment variable is required');
await expect(service.createArchonClient()).rejects.toThrow('Default value: 8051');
await expect(mcpClientService.createArchonClient()).rejects.toThrow('ARCHON_MCP_PORT environment variable is required');
await expect(mcpClientService.createArchonClient()).rejects.toThrow('Default value: 8051');
});
it('should use ARCHON_MCP_PORT when set', async () => {
@@ -205,11 +214,10 @@ describe('MCP Client Service Configuration', () => {
})
});
const { MCPClientService } = await import('../../src/services/mcpClientService');
const service = new MCPClientService();
const { mcpClientService } = await import('../../src/services/mcpClientService');
try {
await service.createArchonClient();
await mcpClientService.createArchonClient();
// Verify the fetch was called with the correct URL
expect(global.fetch).toHaveBeenCalledWith(

View File

@@ -15,7 +15,7 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
// Get host and port from environment variables or use defaults
// For internal Docker communication, use the service name
// For external access, use the HOST from environment
const isDocker = process.env.DOCKER_ENV === 'true' || !!process.env.HOSTNAME;
const isDocker = process.env.DOCKER_ENV === 'true' || existsSync('/.dockerenv');
const internalHost = 'archon-server'; // Docker service name for internal communication
const externalHost = process.env.HOST || 'localhost'; // Host for external access
const host = isDocker ? internalHost : externalHost;
@@ -278,7 +278,7 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
],
server: {
host: '0.0.0.0', // Listen on all network interfaces with explicit IP
port: 5173, // Match the port expected in Docker
port: parseInt(process.env.ARCHON_UI_PORT || env.ARCHON_UI_PORT || '3737'), // Use configurable port
strictPort: true, // Exit if port is in use
proxy: {
'/api': {