Multi-Port Support

Dewy provides functionality to simultaneously manage multiple TCP ports for a single application. This feature enables unified deployment of services with different purposes, such as HTTP and HTTPS, API and WebUI, or production and debug endpoints. Integration with the server-starter library enables independent process management and graceful restarts for each port.

Multi-Port Use Cases

In development environments or resource-constrained environments, there are often requirements for a single application to provide multiple services that would ideally be separated into independent services. Dewy's multi-port functionality is designed to address these complex requirements.

Protocol-Based Separation

In development environments or cost-constrained environments, you may want to provide different protocols like REST API, gRPC, and GraphQL through a single application. Multi-port functionality allows you to support multiple protocols while reducing management costs.

# Provide REST API, gRPC, and GraphQL through a single application
dewy server --registry ghr://myorg/webapp --port 8080,8443,8090 -- /opt/webapp/current/webapp

This configuration allows port 8080 to serve REST API, port 8443 to serve gRPC services, and port 8090 to serve GraphQL endpoints simultaneously, simplifying server management in development environments.

Function-Based Separation

Even within a single application, providing different functions like API, admin interface, and metrics collection on independent ports allows you to apply different security policies and access controls to each function.

# Multiple ports for API, admin interface, and metrics
dewy server --registry ghr://myorg/webapp \
  --port 8080,8090,9090 \
  -- /opt/webapp/current/webapp

In this example, a single application can provide the main API on port 8080, admin interface on port 8090, and Prometheus metrics on port 9090.

Development Environment

Development environments need to provide both production and debug functionality simultaneously. Debug ports can provide detailed log output and profiling information, while production ports allow verification of normal operations.

# Parallel operation of production and debug functionality
dewy server --registry ghr://myorg/devapp \
  --port 8080,8081 \
  -- /opt/devapp/current/devapp --prod-port=8080 --debug-port=8081

Developers can test under the same conditions as production while accessing debug information when needed.

Multi-Port Specification Methods

Dewy allows flexible and intuitive specification of multiple ports. Choose the optimal specification method based on your use case and environment.

Single Port

In the simplest case, specify only one port. This behaves the same as traditional single-port applications.

# Single port specification
dewy server --registry ghr://myorg/app --port 8080 -- /opt/app/current/app
# Or short form
dewy server --registry ghr://myorg/app -p 8080 -- /opt/app/current/app

Multiple Ports

When specifying multiple ports, you can use multiple flags or comma-separated values. Both methods achieve the same result, so choose based on script readability and maintainability considerations.

# Multiple flag specification
dewy server --registry ghr://myorg/app -p 8080 -p 8081 -p 9090 -- /opt/app/current/app

# Comma-separated specification
dewy server --registry ghr://myorg/app --port 8080,8081,9090 -- /opt/app/current/app

The comma-separated method is suitable for configuration file or environment variable management, while the multiple flag method is suitable for dynamic configuration changes.

Port Ranges

When specifying multiple consecutive ports, you can use range specification to write configurations concisely. This feature is particularly useful for load balancing or multi-instance configurations.

# Port range specification (8080 to 8085)
dewy server --registry ghr://myorg/app --port 8080-8085 -- /opt/app/current/app

Range specification has a maximum limit of 100 ports from security and resource management perspectives. If you need more ports, combine multiple range specifications.

Mixed Specification

In actual operations, you can flexibly handle complex requirements by combining different specification methods.

# Combination of comma-separated, range, and multiple flags
dewy server --registry ghr://myorg/app \
  --port 80,443 \
  --port 8080-8085 \
  -p 9090 \
  -- /opt/app/current/app

This example specifies web server ports (80, 443), application instance ports (8080-8085), and metrics port (9090) all at once.

Practical Configuration Examples

Here are specific configuration examples based on actual operational environments.

Development Environment

Development environments require detailed information in an easy-to-understand format for rapid problem identification and resolution.

# Recommended configuration for development environment
dewy server \
  --log-level debug \
  --log-format text \
  --registry ghr://myorg/myapp \
  --port 8080,8081 \
  -- /opt/myapp/current/myapp

Staging Environment

Staging environments test under conditions close to production while collecting information necessary for problem investigation.

# Recommended configuration for staging environment
dewy server \
  --log-level info \
  --log-format json \
  --registry ghr://myorg/myapp \
  --port 8080,8090 \
  -- /opt/myapp/current/myapp

Production Environment

Production environments prioritize performance and record only essential information.

# Recommended configuration for production environment
dewy server \
  --log-level error \
  --log-format json \
  --registry ghr://myorg/myapp \
  --port 8080,8443,9090 \
  -- /opt/myapp/current/myapp

Application Implementation Examples

Here are implementation patterns for applications that utilize dewy's multi-port functionality.

Multi-Port Application Design Patterns

Multi-port applications typically provide different functions or configurations for each port.

package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "os"
    "os/signal"
    "strings"
    "sync"
    "syscall"
    "time"
)

type MultiPortServer struct {
    servers []*http.Server
    wg      sync.WaitGroup
}

func (m *MultiPortServer) Start(ports []string) error {
    for _, port := range ports {
        server := &http.Server{
            Addr:    ":" + port,
            Handler: m.createHandler(port),
        }
        m.servers = append(m.servers, server)

        m.wg.Add(1)
        go func(s *http.Server, p string) {
            defer m.wg.Done()
            log.Printf("Starting server on port %s", p)
            if err := s.ListenAndServe(); err != http.ErrServerClosed {
                log.Printf("Server error on port %s: %v", p, err)
            }
        }(server, port)
    }
    return nil
}

func (m *MultiPortServer) createHandler(port string) http.Handler {
    mux := http.NewServeMux()

    switch port {
    case "8080":
        // Main API
        mux.HandleFunc("/api/", m.apiHandler)
        mux.HandleFunc("/health", m.healthHandler)
    case "8090":
        // Admin interface
        mux.HandleFunc("/admin/", m.adminHandler)
        mux.HandleFunc("/admin/health", m.adminHealthHandler)
    case "9090":
        // Metrics
        mux.HandleFunc("/metrics", m.metricsHandler)
    }

    return mux
}

func (m *MultiPortServer) Shutdown(ctx context.Context) error {
    for _, server := range m.servers {
        if err := server.Shutdown(ctx); err != nil {
            return err
        }
    }
    m.wg.Wait()
    return nil
}

This design provides different functionality on each port while implementing unified shutdown processing.

Retrieving Port Information from Environment Variables

You can dynamically retrieve port information using environment variables provided by server-starter.

package main

import (
    "os"
    "strconv"
    "strings"
)

func getServerStarterPorts() ([]string, error) {
    // Retrieve port information from SERVER_STARTER_PORT environment variable
    portEnv := os.Getenv("SERVER_STARTER_PORT")
    if portEnv == "" {
        // Fallback: application-specific environment variables
        return getApplicationPorts()
    }

    var ports []string
    for _, portSpec := range strings.Split(portEnv, ";") {
        if portSpec == "" {
            continue
        }

        // Parse "name=port" format
        parts := strings.Split(portSpec, "=")
        if len(parts) == 2 {
            ports = append(ports, parts[1])
        }
    }

    return ports, nil
}

func getApplicationPorts() ([]string, error) {
    // Retrieve from application-specific environment variables
    portsStr := os.Getenv("APP_PORTS")
    if portsStr == "" {
        return []string{"8080"}, nil // Default
    }

    return strings.Split(portsStr, ","), nil
}

func main() {
    ports, err := getServerStarterPorts()
    if err != nil {
        log.Fatal(err)
    }

    server := &MultiPortServer{}
    if err := server.Start(ports); err != nil {
        log.Fatal(err)
    }

    // Signal waiting and shutdown processing
    sigCh := make(chan os.Signal, 1)
    signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT, syscall.SIGHUP)

    <-sigCh

    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    if err := server.Shutdown(ctx); err != nil {
        log.Printf("Shutdown error: %v", err)
    }
}

This implementation allows applications to automatically adapt to dewy configuration changes.

Port-Specific Routing Configuration

Large-scale applications require detailed routing configuration for each port.

package main

import (
    "net/http"
    "github.com/gorilla/mux"
)

type PortConfig struct {
    Port        string
    Middlewares []func(http.Handler) http.Handler
    Routes      map[string]http.HandlerFunc
}

func createPortConfigs() map[string]PortConfig {
    return map[string]PortConfig{
        "8080": {
            Port: "8080",
            Middlewares: []func(http.Handler) http.Handler{
                loggingMiddleware,
                corsMiddleware,
                rateLimitMiddleware,
            },
            Routes: map[string]http.HandlerFunc{
                "GET /api/users":     getUsersHandler,
                "POST /api/users":    createUserHandler,
                "GET /api/health":    healthHandler,
            },
        },
        "8090": {
            Port: "8090",
            Middlewares: []func(http.Handler) http.Handler{
                authMiddleware,
                adminLoggingMiddleware,
            },
            Routes: map[string]http.HandlerFunc{
                "GET /admin/dashboard": adminDashboardHandler,
                "GET /admin/users":     adminUsersHandler,
                "POST /admin/config":   adminConfigHandler,
            },
        },
        "9090": {
            Port: "9090",
            Middlewares: []func(http.Handler) http.Handler{
                metricsMiddleware,
            },
            Routes: map[string]http.HandlerFunc{
                "GET /metrics":     metricsHandler,
                "GET /debug/pprof": pprofHandler,
            },
        },
    }
}

func createServer(config PortConfig) *http.Server {
    router := mux.NewRouter()

    // Configure routes
    for pattern, handler := range config.Routes {
        parts := strings.SplitN(pattern, " ", 2)
        if len(parts) == 2 {
            router.HandleFunc(parts[1], handler).Methods(parts[0])
        }
    }

    // Apply middleware
    var handler http.Handler = router
    for i := len(config.Middlewares) - 1; i >= 0; i-- {
        handler = config.Middlewares[i](handler)
    }

    return &http.Server{
        Addr:         ":" + config.Port,
        Handler:      handler,
        ReadTimeout:  15 * time.Second,
        WriteTimeout: 15 * time.Second,
        IdleTimeout:  60 * time.Second,
    }
}

This design allows you to apply different security requirements and performance settings for each port.

Graceful Shutdown for All Ports

Multi-port applications need to execute graceful shutdown simultaneously on all ports.

package main

import (
    "context"
    "log"
    "sync"
    "time"
)

type GracefulServer struct {
    servers []*http.Server
    mu      sync.RWMutex
}

func (g *GracefulServer) AddServer(server *http.Server) {
    g.mu.Lock()
    defer g.mu.Unlock()
    g.servers = append(g.servers, server)
}

func (g *GracefulServer) StartAll() error {
    g.mu.RLock()
    defer g.mu.RUnlock()

    for _, server := range g.servers {
        go func(s *http.Server) {
            log.Printf("Starting server on %s", s.Addr)
            if err := s.ListenAndServe(); err != http.ErrServerClosed {
                log.Printf("Server error on %s: %v", s.Addr, err)
            }
        }(server)
    }

    return nil
}

func (g *GracefulServer) ShutdownAll(timeout time.Duration) error {
    g.mu.RLock()
    defer g.mu.RUnlock()

    ctx, cancel := context.WithTimeout(context.Background(), timeout)
    defer cancel()

    var wg sync.WaitGroup
    errors := make(chan error, len(g.servers))

    for _, server := range g.servers {
        wg.Add(1)
        go func(s *http.Server) {
            defer wg.Done()
            log.Printf("Shutting down server on %s", s.Addr)
            if err := s.Shutdown(ctx); err != nil {
                errors <- err
            }
        }(server)
    }

    // Wait for all servers to complete shutdown
    done := make(chan struct{})
    go func() {
        wg.Wait()
        close(done)
    }()

    select {
    case <-done:
        log.Println("All servers shut down successfully")
        return nil
    case err := <-errors:
        log.Printf("Server shutdown error: %v", err)
        return err
    case <-ctx.Done():
        log.Println("Shutdown timeout reached")
        return ctx.Err()
    }
}

This implementation enables safe service termination on all ports during deployments or maintenance.

Operational Considerations

When operating multi-port configurations in production environments, there are additional elements that don't need consideration in typical single-port configurations. Considering these elements in advance enables stable operations.

Port Number Management and Documentation

When using multiple ports, it's important to clearly manage port number assignments and their purposes. Establish a numbering system that avoids port number conflicts organization-wide and considers future expansion.

# Port number management example
# 8000-8099: Web applications
# 8100-8199: API services
# 8200-8299: Microservices
# 9000-9099: Metrics and monitoring
# 9100-9199: Administration and debugging

In documentation, clearly specify the purpose, protocol, and access control requirements for each port, and share this information across teams. Always update documentation when making configuration changes to prevent operational errors.

Firewall Configuration Integration

Multi-port configurations require port-specific access control according to security policies. Configure appropriate firewall rules based on purpose, such as internal communication, external access, or administrative use.

# Firewall configuration example (ufw)
sudo ufw allow 80/tcp    # HTTP (public)
sudo ufw allow 443/tcp   # HTTPS (public)
sudo ufw allow from 10.0.0.0/8 to any port 8080 # API (internal network only)
sudo ufw allow from 192.168.1.0/24 to any port 9090 # Metrics (management network only)

Combined with network segmentation, you can apply appropriate security levels to each port.

Monitoring and Health Checks

When providing services on multiple ports, you need to monitor the health of each port individually. Design appropriate health check endpoints so that load balancers and monitoring systems can accurately understand the status of each port.

# Prometheus configuration example
- job_name: 'multi-port-app'
  static_configs:
    - targets: ['app.example.com:8080']  # Main API
      labels:
        service: 'api'
    - targets: ['app.example.com:8090']  # Admin interface
      labels:
        service: 'admin'
    - targets: ['app.example.com:9090']  # Metrics
      labels:
        service: 'metrics'

By collecting different metrics for each port and creating service-specific dashboards, you can enable early problem detection and rapid response.

Security and Access Control

Multi-port environments commonly apply different security requirements for each port. Configure appropriate security settings for each port's purpose, such as authentication methods, encryption levels, and access log detail levels.

Apply strong authentication to administrative ports and rate limiting to API ports. Also, prepare mechanisms to quickly disable only affected ports during security incidents.

Troubleshooting

This section covers problems specific to multi-port configurations and their solutions.

Resolving Port Binding Errors

The most common issue is when specified ports are already in use. This occurs when multiple applications are running on the same server or when previous processes haven't terminated properly.

# Check ports in use
sudo netstat -tlnp | grep :8080
sudo lsof -i :8080

# Check and terminate processes
ps aux | grep dewy
sudo kill -TERM <process_id>

# Wait for port to be released
while sudo lsof -i :8080 > /dev/null; do sleep 1; done

When specifying multiple ports, if binding fails on some ports, the entire application may fail to start. Check error logs to identify problematic port numbers and address them.

Addressing Permission Issues

Binding to privileged ports (below 1024) requires root privileges. From a security perspective, proper permission configuration is important in production environments.

# Check permissions
id

# Run with sudo if necessary
sudo dewy server --registry ghr://myorg/app --port 80,443 -- /opt/app/current/app

# Or grant CAP_NET_BIND_SERVICE
sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/dewy

When using systemd, you can specify User=root in the service file or use AmbientCapabilities to grant only necessary privileges.

Log Verification and Debugging Procedures

Debugging multi-port configurations requires detailed log output for each port. Enable dewy's debug mode to check the status of each port in detail.

# Run with debug level
dewy server --log-level debug --registry ghr://myorg/app --port 8080,8081,9090 -- /opt/app/current/app

# Filter logs for specific ports
journalctl -u dewy | grep "port.*8080"

# Real-time log monitoring
tail -f /var/log/dewy.log | grep -E "(port|bind|listen)"

You can also verify that port information is correctly transmitted by checking environment variables from server-starter.

Common Configuration Mistakes and Solutions

Here are common mistakes in configuration files or command-line arguments and their solutions.

Invalid port range specification: Correct invalid range specifications like --port 8080-8081-8082 to --port 8080-8082 or --port 8080,8081,8082.

Duplicate port specification: Duplicates like --port 8080,8080,8081 are automatically removed, but check for unintended duplicates.

Out-of-range port numbers: Port numbers outside the range 1-65535 are invalid. Check your configuration and use numbers within the valid range.

Application configuration mismatch: If ports specified by dewy don't match ports the application actually listens on, connections will fail. Verify that correct port information is passed to the application through environment variables or command-line arguments.