Přejít k navigační liště

Zdroják » Webový vývoj » Docker + Bun + Caddy: mikroslužba za hodinu

Docker + Bun + Caddy: mikroslužba za hodinu

Články Webový vývoj

Potřebujete rychle spustit malou mikroslužbu bez složité infrastruktury, ruční správy certifikátů a konfigurace Nginxu? V tomto článku si ukážeme, jak pomocí Bun, Caddy a Dockeru jako univerzálního prostředí vytvořit a nasadit plnohodnotnou mikroslužbu během jedné hodiny.

Určitě znáte ten moment: chcete si rychle otestovat nový nápad. Spustit malou API službu, která například vrací detaily o SSL certifikátu, nebo resolvuje DSN záznamy dané domény. Jenže místo psaní produktivního kódu skončíte u nekonečné konfigurace Dockeru, Certbotu, reverzní proxy v Nginx a PM2, aby bylo službu vůbec možné spustit a bezpečně provozovat.

Jde to bez zbytečných zádrhelů — s čistým, přehledným a plně funkčním setupem. Zůstane nám Docker jako nositel aplikace, ale odpadne potřeba Nginx, ručního generování certifikátů pomocí Certbotu a PM2 pro persistenci Nodejs serveru.

Ukážeme si, jak pomocí Bun (moderní JavaScript runtime), Caddy serveru pro automatické HTTPS a reverzní proxy a Dockeru jako univerzálního prostředí spustit mikroslužbu během jediné hodiny. Celé řešení je navíc snadno přenositelné a běží bez závislostí na dalších službách.

Protože se jedná o mikroslužbu s minimální zátěží (řádově desítky requestů za hodinu), dovolíme si malý kompromis proti „best practices“ – provozovat dvě služby v jednom Docker kontejneru. V tomto případě to znamená jednodušší nasazení, menší režii a maximální přehlednost. Pro větší projekty je vhodné oddělit reverzní proxy a aplikační server do samostatných kontejnerů.

Náš projekt bude mít následující strukturu:

.
├── .docker
│   ├── Dockerfile        # Build docker kontejneru
│   └── Caddyfile         # Konfigurace Caddy serveru
├── index.ts              # Javascript aplikace
├── docker-bake.hcl       # Předpis pro multi-platform build
├── package.json          # Závislosti a konfigurace
└── bun.lock              # Lock fileCode language: plaintext (plaintext)

Začneme aplikaci

Pro backend jsem zvolil Bun, protože představuje moderní (např. nativní podpora Typescript) a výrazně rychlejší alternativu k tradičnímu Node.js. Bun kombinuje Javascript runtime, balíčkovací systém a buildovací nástroje. Má nižší paměťové nároky a skvěle se hodí nejen pro malé mikroslužby. Vytvořme si tedy nejprve index.ts. Naše aplikace bude jednoduchá, vlastně nás jen pozdraví:

import { serve } from "bun";

const PORT = Bun.env.PORT || 3000;

serve({
  port: PORT,
    routes: {
      '/': () => {
       return new Response("Hello, from Bun!");
      },
      '/api': () => {
       return Response.json({"message": "Hello, from API"}) 
      }
    }
  }
);Code language: TypeScript (typescript)

Využili jsme serve, které je přímo součastí knihoven Bun a umožní nám spustit HTTP server na vybraném portu. Kdybychom potřebovali komplikovanější aplikaci, nebo například ověřování identity uživatele pomocí JWT tokenů, je vhodnější zvolit Express.js nebo ElysiaJS. Bun serve totiž zatím stále neumí middleware. Naši aplikaci si můžeme vyzkoušet pomocí příkazu:

bun run index.tsCode language: Bash (bash)

Předpokládejme, že tím máme hotovo — http://localhost:3000 nás pozdravil — aplikace dělá přesně to, co jsme chtěli.

Build aplikace

Pro sestavení našeho serveru použijeme oficiální Docker image oven/bun:alpine. Nejprve do kontejneru zkopírujeme package.json a bun.lock. Soubor bun.lock nám zajistí, že při instalaci nedojde k žádné změně námi použitých balíčků.

FROM oven/bun:alpine AS build
WORKDIR /app

COPY package.json package.json
COPY bun.lock bun.lock
Code language: Dockerfile (dockerfile)

Povšimněte si také prvního řádku FROM oven/bun:alpine AS build — ten build je totiž název našeho kontejneru, budeme ho ještě potřebovat. Dále spustíme instalaci závislostí:

ENV NODE_ENV=production
RUN bun install --frozen-lockfileCode language: Dockerfile (dockerfile)

a samozřejmě přidat samotnou aplikaci:

COPY index.ts index.tsCode language: Dockerfile (dockerfile)

Poslední krok Dockerfile se postará o build aplikace. Pomocí příkazu bun build se TypeScriptový soubor index.ts přeloží do samostatného spustitelného binárního souboru pojmenovaného myserver. Volby --compile, --minify-whitespace a --minify-syntax zajistí, že výsledný soubor bude zkompilovaný a optimalizovaný, a příkaz chmod +x myserver nastaví soubor jako spustitelný. Díky tomu lze server jednoduše spustit bez potřeby dalšího runtime.

RUN bun build \
  --compile \
  --minify-whitespace \
  --minify-syntax \
  --outfile myserver \
  index.ts &&  \
  chmod +x myserverCode language: Dockerfile (dockerfile)

Nyní se můžeme podívat na nastavení Caddy serveru.

Caddy server

Caddy je moderní webový server a reverzní proxy, který si získal popularitu díky své jednoduchosti a automatickému HTTPS. Na rozdíl od klasického Nginxu nebo Apache nevyžaduje složité konfigurace ani ruční správu certifikátů — Caddy je generuje i obnovuje sám pomocí Let’s Encrypt. Je napsaný v jazyce Go, má velmi nízké nároky na provoz a konfiguraci definuje přehledně v jediném souboru Caddyfile.

Další velkou výhodou je, že se Caddy dá snadno rozšířit pomocí modulů. My jedno takové rozšíření použijeme, jmenuje se caddy-supervisor a postará se nám o persistenci naší Javascript aplikace – tím se zbavíme PM2.

Modul „supervisor“ není součástí základní distribuce a je nutné jej přidat pomocí nástroje xcaddy nebo sestavit vlastní binárku. Do našeho Dockerfile si přidáme následující dva řádky. Povšimněte si opět prvního řádku caddy, bude našeho kontejneru:

FROM caddy:builder AS caddy
RUN xcaddy build --with github.com/baldinof/caddy-supervisor
Code language: Dockerfile (dockerfile)

Uvnitř tohoto kontejneru budeme chtít spustit naši aplikaci na portu 3000, zatímco ven bude dostupná na klasických portech 443 a 80. Základní konfigurace Caddyfile může vypadat velmi jednoduše – stačí tři řádky:

:443 {
  reverse_proxy localhost:3000
}Code language: plaintext (plaintext)

Tímto získáme zabezpečený server dostupný na https://localhost se self-signed certifikátem, který bude reverzní proxy směrovat na localhost:3000. Ale kde je náš supervisor?

Správná poznámka — na portu 3000 zatím nic neběží! Proto bude konfigurace o něco složitější. Aby Caddy spustil náš server na pozadí, přidáme globální nastavení, která se v konfiguraci uzavírají do složených závorek {}.

{
  supervisor {
    myserver {
      restart_policy always
      redirect_stdout stdout
      redirect_stderr stderr
    }
   }
}Code language: plaintext (plaintext)

Dále je vhodné mít přístup k logům, aby bylo možné sledovat chování služby a odchytávat případné problémy. Úroveň podrobnosti logování si můžete zvolit podle potřeby – pro produkci je vhodné volit úspornější režim, zatímco pro testování nebo ladění se hodí nastavit level = debug, aby byly zaznamenány všechny detaily. Takto získáte rychlou zpětnou vazbu o příchozích requestech, chybách a aktivitě Bun serveru i reverzní proxy.

{
 log {
    output stdout
    format console
    level DEBUG
  }
}Code language: plaintext (plaintext)

Pro snadnější nasazení kontejneru do produkce i pohodlné testování na lokálním počítači si upravíme konfiguraci Caddy tak, aby místo pevně daného portu :443 používala proměnnou prostředí ${DOMAIN:localhost}. To znamená, že pokud je nastavena proměnná $DOMAIN, Caddy ji použije; pokud není, fallback je localhost.

Současně jsme také povolili dotazy z libovolného hosta pomocí hlavičky Access-Control-Allow-Origin a definovali metody a hlavičky pro CORS, aby API bylo snadno dostupné z různých frontendů. Konečně, reverse_proxy localhost:3000 směruje požadavky na náš Bun server běžící uvnitř kontejneru.

{$DOMAIN:localhost} {
  encode gzip
  header Access-Control-Allow-Origin "*"
  header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE"
  header Access-Control-Allow-Headers "Content-Type, Authorization"

  reverse_proxy localhost:3000
}Code language: plaintext (plaintext)

Finální sestavení

✓ Máme hotovou aplikaci. ✓ Máme připraven spustitelný soubor. ✓ Máme nastavený Caddy server včetně modulu caddy-supervisor. Pojďme to dát vše dohromady.

Finální fáze našeho Dockerfile bude vypadat následovně, začneme opět nad oven/bun:alpine:

FROM oven/bun:alpine AS bun-app
ENV NODE_ENV=productionCode language: Dockerfile (dockerfile)

Zkopírujeme si Caddy server z caddy kontejneru a přidáme konfigurační soubor Caddyfile:

COPY --from=caddy /usr/bin/caddy /usr/bin/caddy
COPY .docker/Caddyfile /etc/caddy/CaddyfileCode language: Dockerfile (dockerfile)

Do kontejneru přidáme binární soubor myserver, který jsme vytvořili ve fázi build:

COPY --from=build /app/myserver /usr/local/bin/myserverCode language: Dockerfile (dockerfile)

poté spustíme Caddy server a vystavíme porty 80 a 443:

EXPOSE 80 443
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile"]Code language: Dockerfile (dockerfile)

Build Docker kontejneru

Jakmile máme připravený kompletní Dockerfile, můžeme sestavit náš kontejner. Pokud používáte klasický Docker, stačí spustit příkaz:

docker build -t bun-app -f .docker/Dockerfile .Code language: Bash (bash)

Tím vytvoříme image pojmenovaný bun-app. Pokud používáte modernější a rychlejší nástroj Buildx, který podporuje i multi-platformní buildy, můžete využít připravený soubor docker-bake.hcl:

variable "TAG" {
  default = "latest"
}

target "bun-app" {
  context = "."
  dockerfile = ".docker/Dockerfile"
  target = "bun-app"
  platforms = ["linux/amd64", "linux/arm64"]
  pull = true
  tags = [
    "docker.io/username/bun-app:${TAG}",
    "bun-app:${TAG}"
  ]
}Code language: plaintext (plaintext)

Build kontejneru pomocí nástroje buildx spustíte pomocí příkazu:

docker buildx bake bun-app

Pokud jste s buildx nikdy nepracovali, musíte si nejprve vytvořit novou instanci builderu, například takto:

docker buildx create \
  --name multiarch \
  --driver docker-container \
  --useCode language: Bash (bash)

Pokud chceme svůj hotový kontejner poslat do Docker repository, stačí přidat parametr --push:

docker buildx bake bun-app --pushCode language: Bash (bash)

Další možnost je přidat místo --push parametr --load, který načte nově vytvořený kontejneru do vašeho lokálního Dockeru. Existuje zde však jedno omezení, exporter docker zatím neumí s manifest listy pracovat – umí uložit jen jednu architekturu — musíte mu tedy říct, že chcete vytvořit kontejner jen pro svoji platformu:

docker buildx bake bun-app 
  --set bun-app.platform=linux/arm64 \
  --load
Code language: Bash (bash)

Finálním krokem je spuštění na produkci. Předpokládejme, že máme hotový kontejner – buď jsme ho nahráli do Docker Hubu (nebo jiného registry), nebo jsme ho načítali lokálně pomocí parametru --load při buildu. V obou případech je spuštění maximálně jednoduché:

Lokální spuštění

docker run --rm \
  --name bun-app \
  -p "80:80" \
  -p "443:443" \
  -e DOMAIN="https://localhost" \
  bun-app:latestCode language: Bash (bash)

Spuštění na serveru

Na serveru můžete spustit svůj kontejner například pomocí docker compose, případně stačí přidat parametr -d, který zařídí nastartování kontejneru na pozadí:

docker run --name bun-app \
  -d \
  -p 80:80 \
  -p 443:443 \
  -e DOMAIN=api.example.com \
  bun-app:latest
Code language: Bash (bash)

Caddy se postará o automatické HTTPS, reverzní proxy, a díky modulu supervisor také o to, že váš Bun server se po pádu znovu spustí. Takto získáte produkčně nasaditelnou mikroslužbu s plně automatizovaným SSL a minimální konfigurací — a to vše v jediném Docker kontejneru. Kompletní zdrojové kódy naleznete v Docker & Bun & Caddy na mém GitHub.

Komentáře

Odebírat
Upozornit na
guest
1 Komentář
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
Zobrazit všechny komentáře
Martin Havlas

Konečně tady vyšlo něco jiného než PR Články

Strategie a AI jako klíč. Do Prahy přijely špičky technologického světa

WebExpo 2025 ukázalo, jak se tvoří budoucnost. Třídenní technologická konference WebExpo 2025 přivedla do Prahy světové i české experty, kteří nabídli inspiraci napříč obory. Hlavním tématem byla propojenost disciplín, význam AI a potřeba otevřenosti vůči novým výzvám – včetně podpory legální imigrace. Ukázalo se, že inovace vznikají nejen v Silicon Valley, ale i tam, kde se nebojíme myslet jinak.

Přístupnost není jen o splnění norem: nový pohled na inkluzivní design

Přístupnost a inkluze možná nepatří mezi nejžhavější témata digitálního světa – dokud o nich nezačne mluvit Vitaly Friedman. Na WebExpo 2024 předvedl, že inkluzivní design není jen o splněných checkboxech, ale hlavně o lidech. S energií sobě vlastní obrátil zažité přístupy naruby a ukázal, že skutečně přístupný web je nejen možný, ale i nezbytný.