Garuda is built from source with SwiftPM. There are no prebuilt binaries. You
either build the garuda executable from this repository, or add the Garuda
library to your own package and build your application.
Garuda 2 runs on Vega, a work-stealing
runtime for Swift, and takes its database drivers from
vega-db. SwiftPM fetches both. Garuda 1
stays on the release/1.x branch; COMPATIBILITY.md covers
moving from it.
| Swift | 6.2 or newer (swift-tools-version: 6.2). |
| OS | Linux, or macOS 26.4 or newer. Windows is experimental, single-process only (below). |
| zlib | Development files on Linux (zlib1g-dev). Vega links libz for gzip and deflate. macOS has it. |
| A TLS library | Nothing to install by default: TLS uses a BoringSSL vendored in Vega. With the OpenSSL trait, the system's OpenSSL 3 development files. |
Nothing else is linked:
- Databases. The PostgreSQL and Redis drivers are written in Swift and
need no client libraries. SQLite is the system's
libsqlite3, loaded when the first database opens, so it needs no headers to build: installlibsqlite3-0where an application uses it. macOS has it. - brotli and zstd.
--compressloadslibbrotliencandlibzstdat run time where they are installed, and offers only gzip where they are not. - io_uring. Used on Linux 6.7 or later, with no library. An older kernel gets epoll.
macOS 26.4 is Vega's floor: its executor needs the custom-executor hooks that release added to the Swift runtime.
Install a Swift toolchain from swift.org/install with the packages it lists, then the rest:
sudo apt-get install -y --no-install-recommends \
binutils gcc libc6-dev libcurl4 libedit2 libncurses6 \
libsqlite3-0 libxml2 pkg-config tzdata zlib1g-dev ca-certificates
swift --versionAdd libssl-dev to build with the OpenSSL trait.
xcode-select --installThat is all the default build needs. For the OpenSSL trait, install
brew install pkg-config openssl@3 and put Homebrew's OpenSSL where
pkg-config finds it:
export PKG_CONFIG_PATH="$(brew --prefix openssl@3)/lib/pkgconfig"Windows support is being worked on and is experimental. It serves in one
process only: --shards above 0 is refused, Ctrl-C, Ctrl-Break and closing
the console stop it, and response compression is not available yet. Linux and macOS are the supported
platforms.
TLS, and every cryptographic primitive Garuda uses (signed cookies, JWT,
password hashing, QUIC's packet protection), comes from Vega's VegaTLS and
VegaCrypto, over one of two libraries chosen by a package trait:
| trait | library | |
|---|---|---|
BoringSSL |
BoringSSL, vendored in Vega and built with it | the default; no system library needed |
OpenSSL |
the system's OpenSSL 3 | needs its development files; BoringSSL wins if both are on |
swift build -c release # BoringSSL
swift build -c release --traits OpenSSL # the system's OpenSSLA dependent picks the trait on its dependency:
.package(url: "https://github.com/grepjava/garuda", from: "2.0.0", traits: ["OpenSSL"]),There is no kernel TLS in either build.
git clone https://github.com/grepjava/garuda
cd garuda
swift build -c release| product | path | what it is |
|---|---|---|
garuda |
.build/release/garuda |
the server with the benchmark application |
garuda-conformance |
.build/release/garuda-conformance |
routes used by the end-to-end test suites |
pgfuzz |
.build/release/pgfuzz |
the fuzzer (fuzz/README.md) |
Build one with swift build -c release --product garuda.
The binaries link the Swift runtime and zlib dynamically, and OpenSSL with the
OpenSSL trait. A machine that runs them needs those shared libraries.
If the checkout is on a slow file system, such as a Windows drive under WSL, build somewhere else:
swift build -c release --scratch-path ~/garuda-build.build/release/garuda --version
.build/release/garuda --port 8000 &
curl -i http://127.0.0.1:8000/ # 200, empty body
curl http://127.0.0.1:8000/user/42 # 42
curl -i http://127.0.0.1:8000/nope # 404To work on Garuda together with a Vega or vega-db change that is not released yet, point the build at local checkouts:
GARUDA_VEGA_PATH=~/src/vega GARUDA_VEGA_DB_PATH=~/src/vega-db VEGA_PATH=~/src/vega \
swift buildVEGA_PATH is what vega-db reads to find its own Vega; set it to the same
checkout as GARUDA_VEGA_PATH.
Add the package and depend on the products you use:
// swift-tools-version: 6.2
import PackageDescription
let package = Package(
name: "app",
platforms: [.macOS("26.4")],
dependencies: [
.package(url: "https://github.com/grepjava/garuda", from: "2.0.0"),
],
targets: [
.executableTarget(name: "app", dependencies: [
.product(name: "Garuda", package: "garuda"),
]),
]
)| product | what it adds |
|---|---|
Garuda |
the handler API, the server, the HTTP client, sessions, JWT and the rest of the middleware |
GarudaTesting |
app.test: requests through the real HTTP/1 connection over an in-memory stream |
GarudaJSON |
@JSON, which writes a type's JSON reading and writing out instead of leaving it to Codable |
GarudaPostgres |
vega-db's PostgreSQL driver, joined to Garuda |
GarudaRedis |
vega-db's Redis driver |
GarudaSQLite |
vega-db's SQLite driver |
GarudaSQL |
@PostgresRow, which does for a result row what @JSON does for JSON |
GarudaUploads |
resumable uploads (draft-ietf-httpbis-resumable-upload): app.resumableUploads and a file store |
GarudaJSON and GarudaSQL are separate because they are the only parts of
Garuda that need swift-syntax: a target that does not depend on them never
builds the macro plugin, although the package is still fetched when Garuda's
dependencies are resolved. The database products are separate so that an
application builds only the drivers it imports.
CONNECTORS.md covers them.
README.md shows GarudaUploads.
Application.run() reads the flags in CONFIG.md, so your binary is
started the same way as garuda. README.md has a first
application, and HANDLER-API.md the design of the API.
app.run() reads the process's command line. An application that has commands
of its own -- a migrate before it serves -- hands Garuda only the arguments
that are Garuda's. app.run(arguments:) takes an argv, program name first:
#if canImport(Glibc)
import Glibc
#else
import Darwin
#endif
import Garuda
import GarudaPostgres
import Vega
let app = Application()
// … routes …
let databaseURL = "postgres://app@localhost/app?sslmode=disable"
let migrations: [[String]] = [
["create table orders (id bigserial primary key, total_cents bigint not null)"],
]
let arguments = CommandLine.arguments
switch arguments.dropFirst().first ?? "serve" {
case "migrate":
// One-off work, on a runtime of its own, with no server.
let runtime = Runtime(workers: 1)
try runtime.block {
let pool = PostgresPool(try PostgresConfiguration(url: databaseURL))
_ = try await pool.migrate(migrations)
pool.close()
}
exit(0)
case "serve":
var flags = Array(arguments.dropFirst(2))
if flags.first == "--" { flags.removeFirst() }
app.run(arguments: [arguments[0]] + flags)
default:
exit(64)
}Garuda 2 has no app.runOnce. On macOS, a sharded server starts each shard as
a new run of the executable with the same arguments, so main must reach
app.run the same way each time.
HTTP/2 over TLS and HTTP/3 need a certificate. --http3 without one is refused
at start-up.
For local work:
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem \
-days 30 -nodes -subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
.build/release/garuda --port 8443 --tls-cert cert.pem --tls-key key.pem --http3That serves HTTP/1.1 and HTTP/2 on TCP port 8443 and HTTP/3 on UDP port 8443. Open the port for UDP as well as TCP. A missing UDP rule is the usual reason HTTP/3 is never used.
Browsers do not use HTTP/3 with an untrusted certificate. For browser testing,
mkcert installs a local CA the
browser trusts.
Several names. Repeat --tls-cert and --tls-key in pairs. The client's
SNI picks the first certificate that covers the name it asks for, and the
first answers a client that names none.
Public certificates. Garuda 2 has no built-in ACME client. Get the
certificate with certbot or another ACME client, and let the server pick up
renewals with --tls-reload or a SIGHUP. --redirect-http 80 --acme-webroot DIR answers certbot's HTTP-01 challenges on port 80.
CONFIG.md has the details.
swift test
swift build -c release
swift build -c release --product garuda-conformanceThe database tests skip themselves unless a database is reachable. The
end-to-end suites in scripts/ run the release binaries. The shell suites
need curl, and some need openssl or nc. The HTTP/2, HTTP/3, handler and
upload suites are Python test clients that need h2 and aioquic 1.3.0
(pip install h2 aioquic==1.3.0); aioquic 1.7.0 breaks the WebTransport
client and one HTTP/3 cancellation check.
swift test also replays the fuzz corpus (GarudaFuzzTests). The fuzzer
itself runs longer, and is most useful under AddressSanitizer:
swift build -c release --product pgfuzz -Xswiftc -sanitize=address
.build/release/pgfuzz --seconds 60| symptom | cause |
|---|---|
Linker errors naming z |
zlib development files are missing |
Linker errors naming ssl or crypto |
built with the OpenSSL trait without OpenSSL's development files, or pkg-config cannot find them |
requires minimum platform version on macOS |
the package or the machine is below macOS 26.4 |
| HTTP/3 is never used | UDP is closed on the QUIC port, or the client does not trust the certificate |
--compress only ever sends gzip |
libbrotlienc and libzstd are not installed |
Next: CONFIG.md covers every flag, and DEPLOYMENT.md running in production. README.md describes the handler API and current status. ARCHITECTURE.md explains how Garuda is built.