Cheatsheet de Deploy

July 1, 2026 · View on GitHub

Leia em English ou Português (BR).

Mesmo código Horse, sete formatos de deploy, dois transportes assíncronos intercambiáveis (CrossSocket e mORMot2). Esta página é a referência rápida; para o racional e exemplos de código mais longos, veja Providers e Tipos de aplicação §8 (CrossSocket) ou §9 (mORMot2).


O padrão de quatro passos

Todo formato nesta página segue os mesmos quatro passos:

  1. Defina exatamente um transporte em Project Options → Conditional Defines:

    • HORSE_PROVIDER_CROSSSOCKET para o Provider CrossSocket, ou
    • HORSE_PROVIDER_MORMOT para o Provider mORMot2.

    Adicione o HORSE_APPTYPE_* correspondente se quiser a unit de conveniência produto cruzado (desde o PATCH-HORSE-2). O alias legado HORSE_CROSSSOCKET continua funcionando pra compatibilidade; não existe alias legado para mORMot. Os dois defines de Provider são mutuamente exclusivos — o Horse.pas rejeita a combinação em tempo de compilação.

  2. Escolha o tipo de projeto pro formato desejado.

  3. Chame THorse.Listen(port) do hook de ciclo de vida certo daquele formato.

  4. Chame THorse.StopListen do hook de shutdown para que o Provider esgote as requisições ativas.

A verificação em runtime que dirige o comportamento do formato é o IsConsole:

  • IsConsole = True (binário console, {$APPTYPE CONSOLE}) → Listen bloqueia a thread chamadora; o hook de shutdown desbloqueia via StopListen.
  • IsConsole = False (VCL / LCL / TService) → Listen inicia as threads de IO e retorna imediatamente; a thread chamadora fica livre pro message loop da GUI ou pro loop de controle do serviço.

Visão geral

Formato{$APPTYPE CONSOLE}Tipo de projetoListen deStopListen de
Console (Delphi)Console Applicationbegin … end.SetConsoleCtrlHandler
VCL (Delphi)VCL Forms ApplicationFormCreateFormClose
Daemon Linux (Delphi)Console (target Linux64)begin … end.POSIX signal(SIGTERM, …)
Serviço Windows (Delphi)Service ApplicationServiceStart (worker thread)ServiceStop
Daemon Linux (FPC)Console (FPC)begin … end.fpSignal(SIGTERM, …)
LCL desktop (FPC)Lazarus ApplicationFormCreateFormClose
HTTPApplication FPCConsole (FPC)begin … end.fpSignal(SIGTERM, …)

Código mínimo por formato

Console (Delphi) — SetConsoleCtrlHandler para interrupção

{$APPTYPE CONSOLE}
function CtrlHandler(dwCtrlType: DWORD): BOOL; stdcall;
begin
  if dwCtrlType in [CTRL_C_EVENT, CTRL_BREAK_EVENT, CTRL_CLOSE_EVENT, CTRL_SHUTDOWN_EVENT] then
  begin 
    THorse.StopListen; 
    Result := True; 
  end
  else 
    Result := False;
end;
begin
  SetConsoleCtrlHandler(@CtrlHandler, True);
  THorse.Listen(9000);
end.

VCL (Delphi) — FormCreate / FormClose

procedure TfrmMain.FormCreate(Sender: TObject);  
begin 
  THorse.Listen(9000); 
end;

procedure TfrmMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin 
  THorse.StopListen;   
end;

Daemon Linux (Delphi) — signal POSIX + systemd

{$APPTYPE CONSOLE}
{$IFDEF LINUX}
procedure HandleSignal(ASignal: Integer); cdecl;
begin 
  THorse.StopListen; 
end;
{$ENDIF}
begin
  {$IFDEF LINUX} 
  signal(SIGTERM, @HandleSignal); 
  signal(SIGINT, @HandleSignal); 
  {$ENDIF}
  THorse.Listen(9000);
end.

Unit do systemd (/etc/systemd/system/myhorse.service):

[Unit]
After=network.target
[Service]
Type=simple
ExecStart=/opt/myhorse/MyDaemon
Restart=on-failure
[Install]
WantedBy=multi-user.target

Serviço Windows (Delphi) — TService

procedure TMyHorseService.ServiceStart(Sender: TService; var Started: Boolean);
begin
  FListenerThread := TThread.CreateAnonymousThread(
    procedure 
    begin 
      THorse.Listen(9000); 
    end);
  FListenerThread.FreeOnTerminate := False;
  FListenerThread.Start;
  Started := True;
end;

procedure TMyHorseService.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
  THorse.StopListen;
  FListenerThread.WaitFor;
  FreeAndNil(FListenerThread);
  Stopped := True;
end;

Instale / desinstale pelos verbos padrão do SCM:

MyHorseServer.exe /install
sc start MyHorseService
sc stop  MyHorseService
MyHorseServer.exe /uninstall

Alternativa mais simples sem escrever um TService: construa o binário Console acima e embrulhe com NSSMnssm install MyHorseService C:\path\to\Console.exe. O NSSM envia Ctrl+Break no stop, que o CtrlHandler do formato Console pega.

Daemon Linux (FPC) — fpSignal + systemd

{$MODE DELPHI}{$H+}
{$APPTYPE CONSOLE}
{$IFDEF UNIX}
procedure HandleSignal(ASignal: cint); cdecl;
begin THorse.StopListen; end;
{$ENDIF}
begin
  {$IFDEF UNIX} 
  fpSignal(SIGTERM, @HandleSignal); 
  fpSignal(SIGINT, @HandleSignal); 
  {$ENDIF}
  THorse.Listen(9000);
end.

Unit do systemd idêntica ao daemon Linux Delphi acima.

LCL (FPC / Lazarus) — FormCreate / FormClose

procedure TfrmMain.FormCreate(Sender: TObject);
begin 
  THorse.Listen(9000); 
end;

procedure TfrmMain.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin 
  THorse.StopListen; 
end;

HTTPApplication FPC — binário FPC formato console

Mesmo código do daemon Linux FPC — o THorse.Listen é dono do loop; não chame fphttpapp.Application.Run. Se uma biblioteca espera que TFPHTTPApplication exista, instancie ele mas deixe o Run quieto.


Mapeamento de sinais de stop

O sinal de shutdown varia por OS e supervisor. Todos os casos acabam no mesmo lugar: THorse.StopListen → drenagem do contador de requisições ativas SEC-30 do Provider ativo (implementada de forma idêntica em horse-provider-crosssocket e horse-provider-mormot) → Listen retorna → processo sai limpo.

SupervisorSinal enviadoComo seu código pega
Terminal (Ctrl-C)SIGINT / CTRL_C_EVENTSetConsoleCtrlHandler (Windows) / signal / fpSignal (POSIX)
systemdSIGTERMHandler POSIX signal / fpSignal
Windows SCMSERVICE_CONTROL_STOPEvento TService.OnStop
NSSMCtrl+Break (via console)SetConsoleCtrlHandler pegando CTRL_BREAK_EVENT
Fechar janela VCL / LCLWM_CLOSEEvento TForm.OnClose
Docker docker stopSIGTERM (depois SIGKILL após período de graça)Handler POSIX signal / fpSignal — dê < 10 s pro seu handler retornar

Armadilhas comuns

SintomaCausaCorreção
Processo sai imediatamente após o startBinário console sem handler de sinal chega ao end. depois que Listen retornaAdicione o setup de CtrlHandler / signal antes do Listen.
Form VCL trava no startup{$APPTYPE CONSOLE} deixado por engano no .dprRemova essa diretiva; apps VCL/LCL precisam de IsConsole = False.
Serviço Windows trava em "Starting"ServiceStart bloqueia porque Listen foi chamado direto na thread do SCMEncapsule o Listen num TThread.CreateAnonymousThread (veja o snippet TService).
systemd reporta "main process exited, code=killed, status=15/TERM"Processo não pegou SIGTERM — systemd teve que escalarInstale o handler de sinal POSIX pro binário sair limpo com 0.
Address already in use após restartProcesso anterior segurou o socket e foi force-killed (sem drenagem limpa)Sempre chame StopListen; pra Docker, configure --stop-grace-period=30s.
Requisições em andamento perdidas no shutdownListen retornou imediatamente após StopListen sem aguardar o contador de requisições ativasO SEC-30 já cuida disso — garanta que está no horse-provider-crosssocket >= 1.0.4 contra um winddriver/Delphi-Cross-Socket recente (ou o fork freitasjca/Delphi-Cross-Socket v1.0.3), ou qualquer release do horse-provider-mormot, onde o SEC-30 está integrado desde o primeiro dia.
~60 % de HTTP 500 sob carga no Indy (EWebBrokerException: "Maximum number of concurrent connections exceeded"), só com middleware de cabeçalhos de resposta + keep-alive + concorrência ≥ ~40O MaxConnections do pool de módulos do WebBroker tinha default 32Corrigido por padrão — os providers Indy agora elevam o teto para DEFAULT_MAX_CONNECTIONS (1024) quando THorse.MaxConnections não é definido. Defina THorse.MaxConnections := N para ir além. (Somente Indy; ver Providers §10.2.)
Conexões recusadas/descartadas em rajadas no IndyO ListenQueue do Indy tinha default 15Corrigido por padrão — os providers Indy agora usam DEFAULT_LISTEN_QUEUE (511) quando THorse.ListenQueue não é definido; aumente o somaxconn do SO para acompanhar em concorrência muito alta.

Deploy multi-OS

A maioria das equipes entrega o mesmo código Horse como daemon Linux em produção e um Serviço Windows ou Console binário pra dev. Os Conditional Defines ficam iguais — só muda o target do projeto (Win64 / Linux64). Build duas vezes, uma por OS.

Config compartilhada de CI:

jobs:
  build-linux:
    runs-on: ubuntu-latest
    steps: [...]
    env:
      CONFIGURATION: Release
      PLATFORM: Linux64
      DEFINES: HORSE_PROVIDER_CROSSSOCKET

  build-windows:
    runs-on: windows-latest
    steps: [...]
    env:
      CONFIGURATION: Release
      PLATFORM: Win64
      DEFINES: HORSE_PROVIDER_CROSSSOCKET

O mesmo .dpr compila nos dois — só a casca de deploy (unit systemd vs. registro de serviço SCM) muda por OS.


HTTPS / TLS em runtime — o que entregar por OS

Os três providers TLS self-hosted — HORSE_PROVIDER_CROSSSOCKET, HORSE_PROVIDER_MORMOT e HORSE_PROVIDER_ICS — usam OpenSSL para HTTPS. CrossSocket e mORMot fazem dlopen / LoadLibrary da biblioteca compartilhada do sistema no startup; o ICS acompanha suas próprias bibliotecas OpenSSL 3.x/4.x na distribuição. A pilha de transporte fica no seu binário; o OpenSSL não é linkado estaticamente por padrão (exceto via mormot2static do mORMot). Os três habilitam TLS da mesma forma — Config.SSLEnabled := True mais um cert/key no record de config, passados via ListenWithConfig — e os três suportam TLS mútuo via SSLVerifyPeer + um arquivo de CA. Planeje o deploy considerando isso.

Linux

Instale o OpenSSL pelo gerenciador de pacotes da distro pra que libssl.so e libcrypto.so fiquem no caminho do loader:

# Debian / Ubuntu (22.04+, OpenSSL 3.x)
apt install libssl3 libcrypto3

# Debian / Ubuntu (20.04, OpenSSL 1.1.x)
apt install libssl1.1

# RHEL / Rocky / Alma 9.x  (OpenSSL 3.x)
dnf install openssl-libs

# Alpine
apk add openssl libcrypto3 libssl3

CrossSocket e mORMot aceitam tanto 1.1.x quanto 3.x — eles testam no startup. Se o loader não encontrar nenhum dos dois, o binário ainda roda, mas SSLEnabled := True falha no Listen com um erro claro de "no SSL backend available". O ICS usa OpenSSL 3.x/4.x e já embute as bibliotecas necessárias.

Para deploys em containers mínimos ou ambientes air-gapped onde não dá pra contar com os pacotes da distro:

  • CrossSocket: entregue o libssl.so + libcrypto.so compatíveis junto do binário e declare na seção [Service] da unit systemd: Environment="LD_LIBRARY_PATH=/opt/seuapp".
  • mORMot2: o pacote mormot2static inclui uma variante com link estático pra algumas plataformas (mormot2static/static/x86_64-linux no FPC) — veja o samples/tests/README do horse-provider-mormot para o setup completo de Search-path.
  • ICS: somente Delphi (Windows + Linux64). Entregue o OpenSSL .so (Linux) / .dll (Windows) que acompanha a distribuição do ICS junto do binário.

TLS mútuo (mTLS). Os três providers verificam certificados de cliente quando Config.SSLVerifyPeer := True e um arquivo de CA é definido (SSLCACertFile no CrossSocket/mORMot, SSLCAFile no ICS). O tests/TLS-TESTS.md de cada provider tem um teste de integração executável (unidirecional + mTLS). O mTLS no servidor CrossSocket também exige os patches Net.CrossSslSocket.* ou o release do fork (veja o README dele).

Windows

Entregue as DLLs do OpenSSL junto do .exe (não jogue em C:\Windows\System32 e não use uma pasta global no PATH — co-localizar com o binário evita que outros apps com OpenSSL bundled sequestrem o load):

Versão OpenSSLNomes das DLLs (por arquitetura)
1.1.xlibssl-1_1-x64.dll, libcrypto-1_1-x64.dll (Win64); tire o -x64 pra Win32
3.xlibssl-3-x64.dll, libcrypto-3-x64.dll (Win64); tire o -x64 pra Win32

A fonte padrão são os builds oficiais OpenSSL para Windows ou os instaladores da SLProWeb. Escolha uma versão e use ela em todos os ambientes — código que faz dynamic-load de libssl-1_1.dll não roda num host que só tem libcrypto-3.dll, e as duas não convivem no mesmo processo.

Em deploy como Serviço Windows, as DLLs precisam estar na mesma pasta do .exe do serviço — o SCM não herda o PATH do usuário.

Armadilha comum — crash por descasamento de versão

SintomaCausaCorreção
EOSError: failed to load libssl no LinuxNenhum pacote OpenSSL instalado, ou container minimal só com libcInstale libssl3 / libssl1.1 (Linux) ou copie as DLLs junto do binário (Windows).
HTTPS funciona em dev, quebra em prod com "wrong version number"Box de dev tem OpenSSL 3.x; box de prod tem 1.1.x (ou vice-versa) — features de negociação TLS diferemPadronize numa família de versão em todos os ambientes. Se tiver que suportar as duas, entregue as DLLs (Windows) ou use a variante estática do mormot2static (mORMot2, Linux).
SIGSEGV aleatório no handshake TLS no LinuxDuas cópias do libcrypto carregadas ao mesmo tempo (sistema 3.x + uma 1.1.x bundled diferente em LD_LIBRARY_PATH)Garanta que só uma ABI do OpenSSL fica alcançável.

Veja também