🎁
Bonus Top-up 50%!Promo Terbatas
Upvote
tcpwebsocketgrpchelipodnetworkingrealtime

TCP Mode di Helipod: Deploy WebSocket, gRPC, dan Non-HTTP Services

Tim Helipod

7 menit baca

Panduan menggunakan TCP mode di Helipod untuk deploy aplikasi non-HTTP seperti WebSocket server, gRPC service, game server, dan database custom — termasuk konfigurasi helipack.json dan use case nyata.

Helipod by default menggunakan HTTP/HTTPS untuk routing traffic ke container. Ini cocok untuk 90% use case: web app, REST API, GraphQL. Tapi ada skenario di mana kamu butuh protokol TCP murni — WebSocket server yang persistent, gRPC service, game server, atau custom protocol.

Untuk kasus ini, Helipod menyediakan TCP mode — cara menjalankan service yang komunikasinya bukan via HTTP.

Apa itu TCP Mode?

Dalam mode normal (HTTP), Helipod menggunakan Traefik sebagai reverse proxy yang melakukan:

  • SSL termination (HTTPS → HTTP ke container)
  • Health check via HTTP GET
  • Routing berdasarkan hostname

Dalam TCP mode, Traefik meneruskan koneksi TCP secara langsung ke container tanpa memproses HTTP. Ini berarti:

  • Koneksi TCP persistent (tidak di-terminate per request)
  • Tidak ada SSL termination otomatis dari Traefik
  • Health check HTTP dinonaktifkan
  • Cocok untuk protokol apapun yang berjalan di atas TCP

Cara Aktifkan TCP Mode

Di helipack.json:

{
  "run": {
    "port": 9000,
    "isTcp": true,
    "command": "node dist/server.js"
  }
}

Atau jika menggunakan Dockerfile sendiri, pastikan EXPOSE menunjuk port yang benar, dan set isTcp: true di helipack.json.

Penting: Saat isTcp: true, health check HTTP otomatis dinonaktifkan. Helipod menggunakan TCP connection check sebagai gantinya — jika port bisa di-connect, pod dianggap healthy.

Use Case 1: WebSocket Server (Node.js)

WebSocket membutuhkan koneksi persistent antara client dan server. HTTP mode tidak cocok karena Traefik tidak dirancang untuk koneksi HTTP Upgrade yang long-lived.

Setup WebSocket dengan ws library

// src/server.js
const WebSocket = require('ws');

const port = parseInt(process.env.PORT || '8080');
const wss = new WebSocket.Server({ port });

wss.on('connection', (ws, req) => {
  console.log(`Client connected from ${req.socket.remoteAddress}`);
  
  ws.on('message', (message) => {
    console.log('Received:', message.toString());
    
    // Broadcast ke semua client
    wss.clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(message.toString());
      }
    });
  });
  
  ws.on('close', () => {
    console.log('Client disconnected');
  });
  
  ws.send(JSON.stringify({ type: 'connected', message: 'Welcome!' }));
});

console.log(`WebSocket server running on port ${port}`);

helipack.json

{
  "run": {
    "port": 8080,
    "isTcp": true,
    "command": "node src/server.js"
  }
}

Koneksi dari Client

// Client-side JavaScript
const ws = new WebSocket('wss://nama-project.helipod.app');

ws.onopen = () => {
  console.log('Connected!');
  ws.send(JSON.stringify({ type: 'hello', data: 'World' }));
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log('Received:', data);
};

ws.onclose = () => {
  console.log('Disconnected');
};

Use Case 2: WebSocket dengan Socket.IO

Socket.IO lebih popular dari raw WebSocket karena punya fallback, rooms, dan namespace:

// src/index.js
const { createServer } = require('http');
const { Server } = require('socket.io');

const port = parseInt(process.env.PORT || '3000');
const httpServer = createServer();
const io = new Server(httpServer, {
  cors: {
    origin: process.env.ALLOWED_ORIGINS?.split(',') || '*',
    methods: ['GET', 'POST'],
  },
  transports: ['websocket', 'polling'],
});

io.on('connection', (socket) => {
  console.log('User connected:', socket.id);
  
  socket.on('join-room', (roomId) => {
    socket.join(roomId);
    socket.to(roomId).emit('user-joined', socket.id);
  });
  
  socket.on('message', ({ roomId, content }) => {
    io.to(roomId).emit('message', {
      sender: socket.id,
      content,
      timestamp: new Date().toISOString(),
    });
  });
  
  socket.on('disconnect', () => {
    console.log('User disconnected:', socket.id);
  });
});

httpServer.listen(port, () => {
  console.log(`Socket.IO server running on port ${port}`);
});

Catatan Socket.IO: Socket.IO awalnya mencoba HTTP polling sebelum upgrade ke WebSocket. Ini bisa bekerja di HTTP mode juga untuk beberapa kasus. Coba HTTP mode dulu — jika ada masalah dengan koneksi persistent, switch ke TCP mode.

helipack.json untuk Socket.IO

{
  "run": {
    "port": 3000,
    "isTcp": true,
    "command": "node src/index.js"
  }
}

Use Case 3: gRPC Service (Node.js)

gRPC menggunakan HTTP/2 dengan binary protocol (Protocol Buffers). Meski secara teknis adalah HTTP/2, gRPC membutuhkan TCP mode di Helipod karena Traefik perlu dikonfigurasi khusus untuk HTTP/2 passthrough.

Define Proto File

// proto/user.proto
syntax = "proto3";

package user;

service UserService {
  rpc GetUser (GetUserRequest) returns (User);
  rpc CreateUser (CreateUserRequest) returns (User);
  rpc ListUsers (ListUsersRequest) returns (stream User);
}

message User {
  string id = 1;
  string name = 2;
  string email = 3;
}

message GetUserRequest {
  string id = 1;
}

message CreateUserRequest {
  string name = 1;
  string email = 2;
}

message ListUsersRequest {
  int32 limit = 1;
}

gRPC Server

// src/server.js
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const { v4: uuidv4 } = require('uuid');

const packageDef = protoLoader.loadSync('./proto/user.proto');
const userProto = grpc.loadPackageDefinition(packageDef).user;

const users = new Map();

const server = new grpc.Server();

server.addService(userProto.UserService.service, {
  getUser: (call, callback) => {
    const user = users.get(call.request.id);
    if (user) {
      callback(null, user);
    } else {
      callback({
        code: grpc.status.NOT_FOUND,
        message: 'User not found',
      });
    }
  },
  
  createUser: (call, callback) => {
    const user = {
      id: uuidv4(),
      name: call.request.name,
      email: call.request.email,
    };
    users.set(user.id, user);
    callback(null, user);
  },
  
  listUsers: (call) => {
    for (const user of users.values()) {
      call.write(user);
    }
    call.end();
  },
});

const port = process.env.PORT || '50051';
server.bindAsync(
  `0.0.0.0:${port}`,
  grpc.ServerCredentials.createInsecure(),
  (err, port) => {
    if (err) throw err;
    console.log(`gRPC server running on port ${port}`);
    server.start();
  }
);

helipack.json untuk gRPC

{
  "run": {
    "port": 50051,
    "isTcp": true,
    "command": "node src/server.js"
  }
}

Akses dari Service Lain (Internal)

Dari service lain dalam project yang sama, akses gRPC via internal network:

// Di service lain
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');

const packageDef = protoLoader.loadSync('./proto/user.proto');
const userProto = grpc.loadPackageDefinition(packageDef).user;

// Gunakan nama service sebagai hostname
const client = new userProto.UserService(
  'user-service:50051',  // nama-service:port
  grpc.credentials.createInsecure()
);

client.getUser({ id: '123' }, (err, response) => {
  if (err) console.error(err);
  else console.log(response);
});

Use Case 4: Game Server (UDP-over-TCP)

Untuk game server yang membutuhkan low-latency:

// src/gameserver.js
const net = require('net');

const players = new Map();
const port = parseInt(process.env.PORT || '7777');

const server = net.createServer((socket) => {
  const playerId = `player_${Date.now()}`;
  players.set(playerId, socket);
  
  console.log(`Player ${playerId} joined`);
  
  // Send welcome packet
  const welcomePacket = Buffer.from(JSON.stringify({
    type: 'welcome',
    playerId,
    playerCount: players.size,
  }) + '\n');
  socket.write(welcomePacket);
  
  // Broadcast to all players
  for (const [id, playerSocket] of players) {
    if (id !== playerId) {
      playerSocket.write(Buffer.from(JSON.stringify({
        type: 'player_joined',
        playerId,
      }) + '\n'));
    }
  }
  
  socket.on('data', (data) => {
    // Parse and handle game packets
    try {
      const packet = JSON.parse(data.toString().trim());
      
      // Broadcast position update to all other players
      if (packet.type === 'position') {
        for (const [id, playerSocket] of players) {
          if (id !== playerId) {
            playerSocket.write(Buffer.from(JSON.stringify({
              type: 'position',
              playerId,
              x: packet.x,
              y: packet.y,
            }) + '\n'));
          }
        }
      }
    } catch (e) {
      console.error('Invalid packet:', e.message);
    }
  });
  
  socket.on('close', () => {
    players.delete(playerId);
    console.log(`Player ${playerId} left`);
  });
});

server.listen(port, '0.0.0.0', () => {
  console.log(`Game server running on port ${port}`);
});
{
  "run": {
    "port": 7777,
    "isTcp": true,
    "command": "node src/gameserver.js"
  },
  "spec": {
    "cpu": 1,
    "memory": 512
  }
}

Use Case 5: Background Worker Tanpa HTTP Port

Background worker seperti Celery (Python) atau queue worker tidak butuh HTTP port sama sekali — mereka hanya listen ke message queue (Redis, RabbitMQ) dan proses jobs.

{
  "run": {
    "isTcp": true,
    "port": 0,
    "command": "celery -A myproject worker --loglevel=info --concurrency=4"
  }
}
{
  "run": {
    "isTcp": true,
    "port": 0,
    "command": "php artisan queue:work redis --sleep=3 --tries=3 --max-time=3600"
  }
}

Tips: Set port: 0 untuk worker yang tidak butuh port sama sekali. Helipod tidak akan mencoba expose port atau melakukan health check.

Keterbatasan TCP Mode

Tidak ada SSL otomatis: Di TCP mode, Traefik tidak melakukan SSL termination. Jika aplikasimu butuh enkripsi (untuk WebSocket wss:// misalnya), kamu perlu handle SSL di dalam aplikasi, atau gunakan service terpisah sebagai SSL terminator.

Tidak ada HTTP health check: Helipod menggunakan TCP connection check. Pastikan service kamu bisa menerima koneksi TCP di port yang ditentukan.

Tidak ada URL publik dengan custom path: Di HTTP mode, kamu bisa punya beberapa service di satu domain dengan path berbeda (/api/, /ws/, dll). Di TCP mode, setiap service butuh port berbeda atau domain berbeda.

Single port: Setiap service hanya bisa expose satu port di TCP mode.

Internal vs External Access

Internal access (dari service lain dalam project):

  • Gunakan nama service dan port langsung: websocket-service:8080
  • Tidak butuh SSL

External access (dari internet/browser):

  • Gunakan magic domain atau custom domain yang diberikan Helipod
  • Browser modern mendukung WebSocket ke domain yang sama dengan HTTPS

Kesimpulan

TCP mode di Helipod membuka kemungkinan untuk deploy jenis service yang lebih beragam — dari WebSocket real-time, gRPC microservice, game server, sampai background worker yang tidak butuh HTTP sama sekali.

Konfigurasinya sederhana: tambahkan "isTcp": true dan set port yang tepat di helipack.json. Helipod mengurus routing TCP-nya.

Ingin tahu lebih tentang konfigurasi helipack.json? Baca Panduan Lengkap helipack.json.

Punya pertanyaan? Hubungi kami di support@helipod.id atau bergabung ke komunitas di hangar.helipod.io.

Siap coba Helipod?

Deploy aplikasi kamu sekarang. Gratis, tanpa kartu kredit.

Mulai Gratis →