Part 2 of Any language on Flisk. The Fastify example in the BYO doc is three files and a command. This is the same layout. The files are Goose's, because that is the language in the image.
services/shop/goose-api/ Dockerfile server.goose http.c goose-compiler/ # Linux build of the compiler, matching the image arch Create that directory before flisk resource. A Dockerfile that already exists is kept.
Goose's own standard library can read PORT with os.env. It does not ship sockets. The stdlib says networking arrives as extern fn when a program needs it. That is application code, the same way Fastify's listen is application code. Flisk never sees the difference.
server.goose
fn main() { var text: u8[>..] = []; let found = os.env("PORT", text); let port, parsed = parse_int(text); if !found || !parsed || port <= 0 || port > 65535 { abort("PORT missing or not a port"); } let code = serve(port as i32); if code != 0 { abort("serve failed"); } } extern fn serve(port: i32) -> i32; serve must keep running. A BYO container uses unless-stopped unless you set --restart no. A process that exits 0 is started again forever. --restart no is for a migrator, not for this API.
http.c
#include <arpa/inet.h> #include <netinet/in.h> #include <stdio.h> #include <string.h> #include <sys/socket.h> #include <unistd.h> int serve(int port) { int fd = socket(AF_INET, SOCK_STREAM, 0); if (fd < 0) return 1; int on = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons((unsigned short)port); addr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) return 2; if (listen(fd, 16) != 0) return 3; for (;;) { int client = accept(fd, 0, 0); if (client < 0) continue; char req[1024]; int n = (int)read(client, req, sizeof(req) - 1); if (n < 0) n = 0; req[n] = 0; const char *body = "{\"status\":\"ok\"}\n"; char resp; if (strncmp(req, "GET /health", 11) == 0) { snprintf(resp, sizeof(resp), "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: %zu\r\nConnection: close\r\n\r\n%s", strlen(body), body); } else { const char *missing = "not found\n"; snprintf(resp, sizeof(resp), "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\nContent-Length: %zu\r\nConnection: close\r\n\r\n%s", strlen(missing), missing); } (void)write(client, resp, strlen(resp)); close(client); } } INADDR_ANY is 0.0.0.0. The Host header is ignored on purpose. Through flisk up that header is api.<project>.localhost. Rails blocked it until the example allowed any host. Most other checked examples did not care.
Dockerfile
The compiler you copy must be a Linux binary for the image architecture. A macOS goose on your laptop will not run inside Debian. Unpack a Linux release from aardappel/goose into goose-compiler/ so goose-compiler/goose exists.
FROM debian:bookworm-slim AS build RUN apt-get update \ && apt-get install -y --no-install-recommends gcc libc6-dev ca-certificates \ && rm -rf /var/lib/apt/lists/* COPY goose-compiler/ /opt/goose/ ENV PATH="/opt/goose:${PATH}" WORKDIR /src COPY server.goose http.c ./ RUN goose -o server.c server.goose \ && cc server.c http.c -o /out/server -lm -pthread FROM debian:bookworm-slim COPY --from=build /out/server /usr/local/bin/server CMD ["server"] No curl in the runtime image. BYO does not probe with curl.
The check every examples/byo folder uses
cd services/shop/goose-api docker build -t goose-health . docker run --rm -d --name goose-health -e PORT=4000 -p 4000:4000 goose-health curl -fsS http://127.0.0.1:4000/health docker stop goose-health HTTP 200 means this language meets the contract. Part 3 registers it. Part 4 is the Traefik check, which docker run cannot do.
