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.
Top comments (0)