Implement Deno microservices architecture with service discovery and load balancing

Advanced 45 min Jun 15, 2026 25 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Build a production-ready Deno microservices architecture with Consul service discovery, HAProxy load balancing, and comprehensive monitoring using Prometheus. This tutorial covers container orchestration, health checks, and automated failover for scalable applications.

Prerequisites

  • Linux server with sudo access
  • 4GB+ RAM for running multiple services
  • Basic understanding of microservices architecture
  • Familiarity with TypeScript/JavaScript

What this solves

Modern applications need to scale horizontally by breaking into smaller services, but managing multiple Deno microservices becomes complex without proper service discovery and load balancing. This tutorial builds a production-ready architecture where Deno services automatically register themselves with Consul, HAProxy distributes traffic based on health checks, and Prometheus monitors the entire stack.

Step-by-step installation

Update system packages

Start by updating your package manager to ensure you get the latest versions of all components.

sudo apt update && sudo apt upgrade -y
sudo apt install -y curl wget unzip software-properties-common
sudo dnf update -y
sudo dnf install -y curl wget unzip

Install Deno runtime

Install Deno using the official installation script, then verify the installation.

curl -fsSL https://deno.land/install.sh | sh
echo 'export DENO_INSTALL="$HOME/.deno"' >> ~/.bashrc
echo 'export PATH="$DENO_INSTALL/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
deno --version

Install Consul for service discovery

Download and install HashiCorp Consul for service registration and health checking.

CONSUL_VERSION="1.17.0"
wget https://releases.hashicorp.com/consul/${CONSUL_VERSION}/consul_${CONSUL_VERSION}_linux_amd64.zip
unzip consul_${CONSUL_VERSION}_linux_amd64.zip
sudo mv consul /usr/local/bin/
sudo chmod +x /usr/local/bin/consul
consul version

Configure Consul server

Create Consul configuration directory and setup the main configuration file.

sudo mkdir -p /etc/consul.d /opt/consul
sudo useradd --system --home /etc/consul.d --shell /bin/false consul
sudo chown -R consul:consul /etc/consul.d /opt/consul
datacenter = "dc1"
data_dir = "/opt/consul"
log_level = "INFO"
server = true
bootstrap_expect = 1
bind_addr = "0.0.0.0"
client_addr = "0.0.0.0"
ui_config {
  enabled = true
}
connect {
  enabled = true
}
ports {
  grpc = 8502
}
acl = {
  enabled = false
  default_policy = "allow"
}

Create Consul systemd service

Setup Consul to run as a systemd service with automatic restarts.

[Unit]
Description=Consul
Requires=network-online.target
After=network-online.target
ConditionFileNotEmpty=/etc/consul.d/consul.hcl

[Service]
Type=notify
User=consul
Group=consul
ExecStart=/usr/local/bin/consul agent -config-dir=/etc/consul.d/
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable consul
sudo systemctl start consul
sudo systemctl status consul

Install HAProxy load balancer

Install HAProxy for distributing traffic across Deno microservices with health checks.

sudo apt install -y haproxy
sudo dnf install -y haproxy

Configure HAProxy with Consul integration

Setup HAProxy configuration with service discovery integration and health checks.

global
    daemon
    maxconn 4096
    log stdout local0
    stats socket /var/run/haproxy.sock mode 660 level admin
    stats timeout 30s

defaults
    mode http
    timeout connect 5s
    timeout client 30s
    timeout server 30s
    option httplog
    option dontlognull
    option redispatch
    retries 3

frontend api_gateway
    bind *:80
    bind *:443 ssl crt /etc/ssl/certs/haproxy.pem
    redirect scheme https if !{ ssl_fc }
    
    # Route based on URL path
    acl is_users_service path_beg /api/users
    acl is_orders_service path_beg /api/orders
    acl is_health path /health
    
    use_backend users_service if is_users_service
    use_backend orders_service if is_orders_service
    use_backend health_check if is_health
    default_backend api_default

backend users_service
    balance roundrobin
    option httpchk GET /health
    http-check expect status 200
    # Dynamic backend discovery via Consul
    server-template users 3 _users._tcp.service.consul:8080 check resolvers consul

backend orders_service
    balance roundrobin
    option httpchk GET /health
    http-check expect status 200
    server-template orders 3 _orders._tcp.service.consul:8081 check resolvers consul

backend api_default
    balance roundrobin
    server default 127.0.0.1:8000 check

backend health_check
    http-request return status 200 content-type text/plain string "HAProxy healthy"

resolvers consul
    nameserver consul 127.0.0.1:8600
    accepted_payload_size 8192
    hold valid 5s

listen stats
    bind *:8404
    stats enable
    stats uri /
    stats refresh 5s
    stats admin if TRUE

Install Prometheus for monitoring

Download and install Prometheus to monitor your microservices architecture.

PROMETHEUS_VERSION="2.48.0"
wget https://github.com/prometheus/prometheus/releases/download/v${PROMETHEUS_VERSION}/prometheus-${PROMETHEUS_VERSION}.linux-amd64.tar.gz
tar xvf prometheus-${PROMETHEUS_VERSION}.linux-amd64.tar.gz
sudo mv prometheus-${PROMETHEUS_VERSION}.linux-amd64/prometheus /usr/local/bin/
sudo mv prometheus-${PROMETHEUS_VERSION}.linux-amd64/promtool /usr/local/bin/
sudo mkdir -p /etc/prometheus /var/lib/prometheus
sudo useradd --system --home /var/lib/prometheus --shell /bin/false prometheus
sudo chown prometheus:prometheus /etc/prometheus /var/lib/prometheus

Configure Prometheus with service discovery

Setup Prometheus to automatically discover services registered in Consul.

global:
  scrape_interval: 15s
  evaluation_interval: 15s

rule_files:
  - "/etc/prometheus/rules/*.yml"

alerting:
  alertmanagers:
    - static_configs:
        - targets:
          - localhost:9093

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  - job_name: 'consul'
    static_configs:
      - targets: ['localhost:8500']
    metrics_path: /v1/agent/metrics
    params:
      format: ['prometheus']

  - job_name: 'haproxy'
    static_configs:
      - targets: ['localhost:8404']
    metrics_path: /stats/prometheus

  - job_name: 'consul-services'
    consul_sd_configs:
      - server: 'localhost:8500'
        services: ['users', 'orders']
    relabel_configs:
      - source_labels: [__meta_consul_service]
        target_label: job
      - source_labels: [__meta_consul_node]
        target_label: instance
      - source_labels: [__meta_consul_service_address]
        target_label: __address__
      - source_labels: [__meta_consul_service_port]
        target_label: __address__
        regex: '(.*)'
        replacement: '${1}:${__meta_consul_service_port}'

  - job_name: 'deno-services'
    consul_sd_configs:
      - server: 'localhost:8500'
        tags: ['deno', 'microservice']
    relabel_configs:
      - source_labels: [__meta_consul_service]
        target_label: service
      - source_labels: [__address__]
        target_label: __address__
        regex: '([^:]+):(\d+)'
        replacement: '${1}:${2}'
    metrics_path: '/metrics'

Create Prometheus systemd service

Setup Prometheus to run as a systemd service with proper permissions.

[Unit]
Description=Prometheus
Wants=network-online.target
After=network-online.target

[Service]
User=prometheus
Group=prometheus
Type=simple
ExecStart=/usr/local/bin/prometheus \
    --config.file /etc/prometheus/prometheus.yml \
    --storage.tsdb.path /var/lib/prometheus/ \
    --web.console.templates=/etc/prometheus/consoles \
    --web.console.libraries=/etc/prometheus/console_libraries \
    --web.listen-address=0.0.0.0:9090 \
    --web.enable-lifecycle
Restart=always

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable prometheus
sudo systemctl start prometheus

Create Deno microservice template

Create a reusable template for Deno microservices with service registration and metrics.

mkdir -p ~/deno-microservices
cd ~/deno-microservices
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";

export interface ServiceConfig {
  name: string;
  port: number;
  version: string;
  consulUrl?: string;
}

export class MicroService {
  private config: ServiceConfig;
  private routes: Map Promise> = new Map();
  private metrics = {
    requests_total: 0,
    requests_duration: [] as number[],
    errors_total: 0,
  };

  constructor(config: ServiceConfig) {
    this.config = {
      consulUrl: "http://localhost:8500",
      ...config,
    };
    this.setupDefaultRoutes();
  }

  private setupDefaultRoutes() {
    this.routes.set("/health", async () => {
      return new Response(JSON.stringify({ status: "healthy", service: this.config.name }), {
        status: 200,
        headers: { "Content-Type": "application/json" },
      });
    });

    this.routes.set("/metrics", async () => {
      const metrics = [
        # HELP http_requests_total Total number of HTTP requests,
        # TYPE http_requests_total counter,
        http_requests_total{service="${this.config.name}"} ${this.metrics.requests_total},
        # HELP http_request_duration_seconds HTTP request duration in seconds,
        # TYPE http_request_duration_seconds histogram,
        http_request_duration_seconds_sum{service="${this.config.name}"} ${this.metrics.requests_duration.reduce((a, b) => a + b, 0)},
        http_request_duration_seconds_count{service="${this.config.name}"} ${this.metrics.requests_duration.length},
        # HELP http_errors_total Total number of HTTP errors,
        # TYPE http_errors_total counter,
        http_errors_total{service="${this.config.name}"} ${this.metrics.errors_total},
      ].join("\n");
      
      return new Response(metrics, {
        status: 200,
        headers: { "Content-Type": "text/plain" },
      });
    });
  }

  addRoute(path: string, handler: (req: Request) => Promise) {
    this.routes.set(path, handler);
  }

  private async handleRequest(req: Request): Promise {
    const start = Date.now();
    this.metrics.requests_total++;
    
    try {
      const url = new URL(req.url);
      const handler = this.routes.get(url.pathname);
      
      if (!handler) {
        this.metrics.errors_total++;
        return new Response("Not Found", { status: 404 });
      }
      
      const response = await handler(req);
      const duration = (Date.now() - start) / 1000;
      this.metrics.requests_duration.push(duration);
      
      if (response.status >= 400) {
        this.metrics.errors_total++;
      }
      
      return response;
    } catch (error) {
      this.metrics.errors_total++;
      console.error(Error handling request:, error);
      return new Response("Internal Server Error", { status: 500 });
    }
  }

  private async registerWithConsul() {
    const serviceDefinition = {
      ID: ${this.config.name}-${Deno.env.get("HOSTNAME") || "localhost"}-${this.config.port},
      Name: this.config.name,
      Tags: ["deno", "microservice", this.config.version],
      Address: Deno.env.get("SERVICE_ADDRESS") || "localhost",
      Port: this.config.port,
      Check: {
        HTTP: http://localhost:${this.config.port}/health,
        Interval: "10s",
        Timeout: "3s",
      },
      Meta: {
        version: this.config.version,
        runtime: "deno",
      },
    };

    try {
      const response = await fetch(${this.config.consulUrl}/v1/agent/service/register, {
        method: "PUT",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(serviceDefinition),
      });
      
      if (response.ok) {
        console.log(Service ${this.config.name} registered with Consul);
      } else {
        console.error("Failed to register with Consul:", await response.text());
      }
    } catch (error) {
      console.error("Error registering with Consul:", error);
    }
  }

  async start() {
    await this.registerWithConsul();
    
    console.log(Starting ${this.config.name} service on port ${this.config.port});
    
    await serve(this.handleRequest.bind(this), {
      port: this.config.port,
      onListen: ({ port, hostname }) => {
        console.log(${this.config.name} service listening on http://${hostname}:${port});
      },
    });
  }
}

Create users microservice

Build the first microservice for user management with Consul registration.

import { MicroService } from "./service-base.ts";

const service = new MicroService({
  name: "users",
  port: 8080,
  version: "1.0.0",
});

// Mock user data
const users = new Map([
  ["1", { id: "1", name: "Alice Johnson", email: "alice@example.com" }],
  ["2", { id: "2", name: "Bob Smith", email: "bob@example.com" }],
  ["3", { id: "3", name: "Carol Wilson", email: "carol@example.com" }],
]);

// GET /api/users
service.addRoute("/api/users", async (req: Request) => {
  if (req.method !== "GET") {
    return new Response("Method Not Allowed", { status: 405 });
  }
  
  return new Response(JSON.stringify(Array.from(users.values())), {
    status: 200,
    headers: { "Content-Type": "application/json" },
  });
});

// GET /api/users/:id
service.addRoute("/api/users/", async (req: Request) => {
  if (req.method !== "GET") {
    return new Response("Method Not Allowed", { status: 405 });
  }
  
  const url = new URL(req.url);
  const id = url.pathname.split("/").pop();
  
  if (!id || !users.has(id)) {
    return new Response("User not found", { status: 404 });
  }
  
  return new Response(JSON.stringify(users.get(id)), {
    status: 200,
    headers: { "Content-Type": "application/json" },
  });
});

if (import.meta.main) {
  service.start();
}

Create orders microservice

Build the second microservice for order management with service discovery.

import { MicroService } from "./service-base.ts";

const service = new MicroService({
  name: "orders",
  port: 8081,
  version: "1.0.0",
});

// Mock order data
const orders = new Map([
  ["1", { id: "1", userId: "1", items: ["laptop", "mouse"], total: 1299.99, status: "shipped" }],
  ["2", { id: "2", userId: "2", items: ["phone"], total: 899.99, status: "processing" }],
  ["3", { id: "3", userId: "1", items: ["keyboard"], total: 129.99, status: "delivered" }],
]);

// GET /api/orders
service.addRoute("/api/orders", async (req: Request) => {
  if (req.method !== "GET") {
    return new Response("Method Not Allowed", { status: 405 });
  }
  
  const url = new URL(req.url);
  const userId = url.searchParams.get("userId");
  
  let result = Array.from(orders.values());
  
  if (userId) {
    result = result.filter(order => order.userId === userId);
  }
  
  return new Response(JSON.stringify(result), {
    status: 200,
    headers: { "Content-Type": "application/json" },
  });
});

// GET /api/orders/:id
service.addRoute("/api/orders/", async (req: Request) => {
  if (req.method !== "GET") {
    return new Response("Method Not Allowed", { status: 405 });
  }
  
  const url = new URL(req.url);
  const id = url.pathname.split("/").pop();
  
  if (!id || !orders.has(id)) {
    return new Response("Order not found", { status: 404 });
  }
  
  return new Response(JSON.stringify(orders.get(id)), {
    status: 200,
    headers: { "Content-Type": "application/json" },
  });
});

if (import.meta.main) {
  service.start();
}

Create systemd services for Deno microservices

Setup systemd services to manage the Deno microservices lifecycle with automatic restarts.

[Unit]
Description=Deno Users Microservice
After=network.target consul.service
Requires=consul.service

[Service]
Type=simple
User=deno
Group=deno
WorkingDirectory=/home/deno/microservices
ExecStart=/home/deno/.deno/bin/deno run --allow-net --allow-env users-service.ts
Restart=always
RestartSec=5
Environment=SERVICE_ADDRESS=localhost
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
[Unit]
Description=Deno Orders Microservice
After=network.target consul.service
Requires=consul.service

[Service]
Type=simple
User=deno
Group=deno
WorkingDirectory=/home/deno/microservices
ExecStart=/home/deno/.deno/bin/deno run --allow-net --allow-env orders-service.ts
Restart=always
RestartSec=5
Environment=SERVICE_ADDRESS=localhost
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Create dedicated user and setup permissions

Create a dedicated user for running Deno services securely with minimal permissions.

sudo useradd --system --home /home/deno --create-home --shell /bin/bash deno
sudo mkdir -p /home/deno/microservices
sudo cp ~/deno-microservices/* /home/deno/microservices/
sudo chown -R deno:deno /home/deno
sudo chmod 755 /home/deno/microservices
sudo chmod 644 /home/deno/microservices/*.ts
Never use chmod 777. It gives every user on the system full access to your files. Instead, fix ownership with chown and use minimal permissions like 755 for directories and 644 for files.

Start all services

Enable and start all the services in the correct order with dependency checking.

# Start Consul first
sudo systemctl status consul

Start HAProxy

sudo systemctl enable haproxy sudo systemctl start haproxy sudo systemctl status haproxy

Start Prometheus

sudo systemctl status prometheus

Start Deno microservices

sudo systemctl daemon-reload sudo systemctl enable deno-users deno-orders sudo systemctl start deno-users deno-orders sudo systemctl status deno-users sudo systemctl status deno-orders

Verify your setup

Test the complete microservices architecture with service discovery and load balancing.

# Check Consul UI and registered services
curl -s http://localhost:8500/v1/agent/services | jq
curl -s http://localhost:8500/v1/health/service/users | jq
curl -s http://localhost:8500/v1/health/service/orders | jq

Test direct service access

curl -s http://localhost:8080/health | jq curl -s http://localhost:8081/health | jq

Test load balancer endpoints

curl -s http://localhost:80/api/users | jq curl -s http://localhost:80/api/orders | jq

Check HAProxy stats

curl -s http://localhost:8404/

Verify Prometheus targets

curl -s http://localhost:9090/api/v1/targets | jq '.data.activeTargets[] | select(.labels.job | contains("consul"))'

Test service metrics

curl -s http://localhost:8080/metrics curl -s http://localhost:8081/metrics

Verify service registration in Consul

consul catalog services consul catalog nodes -service=users consul catalog nodes -service=orders

Configure inter-service communication

Setup secure communication between microservices using service discovery.

import { MicroService } from "./service-base.ts";

const gateway = new MicroService({
  name: "api-gateway",
  port: 8000,
  version: "1.0.0",
});

class ServiceDiscovery {
  private consulUrl: string;
  
  constructor(consulUrl = "http://localhost:8500") {
    this.consulUrl = consulUrl;
  }
  
  async getServiceEndpoint(serviceName: string): Promise {
    try {
      const response = await fetch(${this.consulUrl}/v1/health/service/${serviceName}?passing=true);
      const services = await response.json();
      
      if (services.length === 0) {
        return null;
      }
      
      // Simple round-robin selection
      const service = services[Math.floor(Math.random() * services.length)];
      return http://${service.Service.Address}:${service.Service.Port};
    } catch (error) {
      console.error(Failed to discover service ${serviceName}:, error);
      return null;
    }
  }
  
  async proxyRequest(serviceName: string, path: string, req: Request): Promise {
    const endpoint = await this.getServiceEndpoint(serviceName);
    
    if (!endpoint) {
      return new Response(JSON.stringify({ error: Service ${serviceName} unavailable }), {
        status: 503,
        headers: { "Content-Type": "application/json" },
      });
    }
    
    try {
      const targetUrl = ${endpoint}${path};
      const response = await fetch(targetUrl, {
        method: req.method,
        headers: req.headers,
        body: req.body,
      });
      
      return new Response(response.body, {
        status: response.status,
        statusText: response.statusText,
        headers: response.headers,
      });
    } catch (error) {
      console.error(Error proxying to ${serviceName}:, error);
      return new Response(JSON.stringify({ error: "Service communication failed" }), {
        status: 502,
        headers: { "Content-Type": "application/json" },
      });
    }
  }
}

const discovery = new ServiceDiscovery();

// Route aggregation endpoint
gateway.addRoute("/api/users", async (req: Request) => {
  return await discovery.proxyRequest("users", "/api/users", req);
});

gateway.addRoute("/api/orders", async (req: Request) => {
  return await discovery.proxyRequest("orders", "/api/orders", req);
});

// Aggregated user profile endpoint
gateway.addRoute("/api/profile", async (req: Request) => {
  const url = new URL(req.url);
  const userId = url.searchParams.get("userId");
  
  if (!userId) {
    return new Response(JSON.stringify({ error: "userId parameter required" }), {
      status: 400,
      headers: { "Content-Type": "application/json" },
    });
  }
  
  try {
    // Fetch user data
    const userEndpoint = await discovery.getServiceEndpoint("users");
    const userResponse = await fetch(${userEndpoint}/api/users/${userId});
    const user = await userResponse.json();
    
    // Fetch user orders
    const ordersEndpoint = await discovery.getServiceEndpoint("orders");
    const ordersResponse = await fetch(${ordersEndpoint}/api/orders?userId=${userId});
    const orders = await ordersResponse.json();
    
    const profile = {
      user,
      orders,
      orderCount: orders.length,
      totalSpent: orders.reduce((sum: number, order: any) => sum + order.total, 0),
    };
    
    return new Response(JSON.stringify(profile), {
      status: 200,
      headers: { "Content-Type": "application/json" },
    });
  } catch (error) {
    return new Response(JSON.stringify({ error: "Failed to aggregate profile data" }), {
      status: 500,
      headers: { "Content-Type": "application/json" },
    });
  }
});

if (import.meta.main) {
  gateway.start();
}

Setup monitoring and alerting

Configure Prometheus alerting rules for microservices health and performance monitoring.

sudo mkdir -p /etc/prometheus/rules
groups:
  - name: microservices
    rules:
      - alert: ServiceDown
        expr: up{job=~"consul-services"} == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Microservice {{ $labels.service }} is down"
          description: "{{ $labels.service }} has been down for more than 1 minute"
      
      - alert: HighErrorRate
        expr: rate(http_errors_total[5m]) / rate(http_requests_total[5m]) > 0.1
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "High error rate on {{ $labels.service }}"
          description: "Error rate is {{ $value | humanizePercentage }} for {{ $labels.service }}"
      
      - alert: HighLatency
        expr: histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) > 0.5
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High latency on {{ $labels.service }}"
          description: "95th percentile latency is {{ $value }}s for {{ $labels.service }}"
      
      - alert: ConsulServiceUnhealthy
        expr: consul_health_service_status{status!="passing"} == 1
        for: 30s
        labels:
          severity: critical
        annotations:
          summary: "Consul health check failing for {{ $labels.service_name }}"
          description: "Health check {{ $labels.check_name }} is failing for {{ $labels.service_name }}"
      
      - alert: HAProxyBackendDown
        expr: haproxy_server_status{state!="UP"} == 1
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "HAProxy backend {{ $labels.server }} is down"
          description: "Backend server {{ $labels.server }} in {{ $labels.proxy }} is not responding"
sudo systemctl restart prometheus
sudo systemctl status prometheus

Common issues

Symptom Cause Fix
Service registration fails Consul agent not running or network issue sudo systemctl status consul, check /var/log/consul.log
HAProxy shows backend as down Health check URL returns non-200 status Test curl http://localhost:8080/health, verify service is running
Deno service fails to start Permission issues or missing dependencies Check journalctl -u deno-users, verify file ownership
Prometheus can't scrape services Service discovery not finding targets Check Consul API: curl http://localhost:8500/v1/catalog/services
Load balancer returns 503 No healthy backends available Check HAProxy stats at http://localhost:8404
Inter-service communication fails Network policies or service not registered Test direct service URLs, check Consul service catalog

Next steps

Running this in production?

Want this handled for you? Running microservices at scale adds a second layer of work: capacity planning, failover drills, cost control, and on-call. Our managed platform covers monitoring, backups and 24/7 response by default.

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle managed devops services for businesses that depend on uptime. From initial setup to ongoing operations.