Repository navigation
Reference Server
The reference server is a configuration for Caddy, tools/server/Caddyfile, that makes Caddy a read-only server of vgl pack files, with range requests, CORS, the right caching headers and HTTPS, and that is safe to expose on a public endpoint. It needs Caddy 2.11 or later and no plugins. This page explains what it serves and refuses, how to run it locally and in production (Linux with systemd, Docker, Windows), the headers it sends, how tools/server/check.sh verifies it, and how to adapt it. For hosting in general, including object stores, see Hosting packs.
-
Development. Try packs over HTTP locally, with the samples'
--pack http://..., before uploading them anywhere. - Local networks, private and offline sites. A machine on the network serves packs to every installation, without internet access.
- Full control over a public endpoint: your own machine, your own domain, automatic certificates.
For public data read by many users, an object store or a CDN is usually simpler (Hosting packs); the reference server also works well as the origin behind a CDN.
Put the packs in one directory, each in a subdirectory of its own, exactly as the baker wrote them:
/srv/packs/
gebco/
pack.vglm
base-1cd9fe31.vgls
glo30-norway/
pack.vglm
base-....vgls
shards/<y>/<x>-<digest>.vgls
A pack is then at <site>/<subdirectory>, such as https://packs.example.org/gebco, which is the URI to pass to vgl_pack_create and to the samples' --pack. The baker's .bake directory may stay or go: hidden paths are never served.
The configuration reads three environment variables:
| Variable | Default | Meaning |
|---|---|---|
VGL_SITE |
:8080 |
The address to serve. A domain name (packs.example.org) serves HTTPS with a certificate Caddy obtains and renews (from Let's Encrypt or ZeroSSL), and redirects HTTP to it. A port (:8080) serves plain HTTP, for local use or behind a proxy or CDN that terminates TLS. |
VGL_PACKS |
packs |
The directory of packs, relative to the working directory unless absolute. |
VGL_CORS_ORIGIN |
* |
The origin that browsers may read packs from. Packs are public data, so * is usually right; name an origin (https://app.example.org) to keep other sites from using your bandwidth from browsers. |
The configuration turns Caddy's admin endpoint off, so restart Caddy to change settings.
Install Caddy 2.11 or later (CI uses 2.11.7 from Caddy's releases). From the repository root:
# Plain HTTP on port 8080, serving the sample packs.
VGL_PACKS=samples/data/packs caddy run --config tools/server/CaddyfileThen, from another terminal:
curl -I http://127.0.0.1:8080/gebco/pack.vglm
vgl-bake check http://127.0.0.1:8080/gebco
build/linux-gcc-shared/bin/Release/vgl-headless --pack http://127.0.0.1:8080/gebco \
--pack http://127.0.0.1:8080/glo30-sognefjordThe samples need a vgl built with VGL_DEFAULTS_HTTP (the shared presets turn it on) to read http:// URIs (Samples). Caddy logs every request to standard output.
On Windows, in PowerShell:
$env:VGL_PACKS = "D:\packs"
caddy run --config tools\server\CaddyfileTo serve the web build of the OpenGL sample together with packs, use samples/gl/web/Caddyfile instead: it serves the page cross-origin isolated on port 8090 and the sample packs at data/packs, the same way the reference server serves them.
Point the domain's DNS records (A and AAAA) at the machine, open ports 80 and 443 (TCP) and 443 (UDP, for HTTP/3), and set VGL_SITE to the domain:
VGL_SITE=packs.example.org VGL_PACKS=/srv/packs caddy run --config tools/server/CaddyfilePort 80 is needed to obtain certificates and to redirect to HTTPS. Behind a CDN or proxy that terminates TLS, set VGL_SITE to a port instead and let the CDN connect to it.
Install the Caddyfile as /etc/caddy/Caddyfile and run Caddy as an unprivileged user with read-only access to the system:
[Unit]
Description=vgl pack server
After=network-online.target
Wants=network-online.target
[Service]
User=caddy
Group=caddy
Environment=VGL_SITE=packs.example.org VGL_PACKS=/srv/packs
ExecStart=/usr/bin/caddy run --config /etc/caddy/Caddyfile
AmbientCapabilities=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/lib/caddy
Restart=on-failure
[Install]
WantedBy=multi-user.targetSave it as, for example, /etc/systemd/system/vgl-packs.service, then:
sudo systemctl daemon-reload
sudo systemctl enable --now vgl-packs
journalctl -u vgl-packs -f # the access logReadWritePaths=/var/lib/caddy is where Caddy keeps its certificates (the caddy user's data directory as Linux packages of Caddy set it up). If your distribution's package already installs a caddy.service, disable it or replace its Caddyfile, so the two do not compete for ports 80 and 443.
The repository has no Dockerfile; Caddy's official image runs the configuration as it is. The caddy-data volume keeps the certificates across restarts:
docker run -d --name vgl-packs --restart unless-stopped \
-p 80:80 -p 443:443 -p 443:443/udp \
-e VGL_SITE=packs.example.org -e VGL_PACKS=/srv/packs \
-v /srv/packs:/srv/packs:ro \
-v "$PWD/tools/server/Caddyfile:/etc/caddy/Caddyfile:ro" \
-v caddy-data:/data \
caddy:2.11.7The packs are mounted read-only. For local use, publish one port and leave VGL_SITE at its default: -p 8080:8080 without -e VGL_SITE=....
Caddy runs on Windows from the same configuration. Set the variables and run it as above, or run it as a Windows service with a service wrapper, as Caddy's documentation describes; set VGL_SITE, VGL_PACKS (an absolute path) and, if needed, VGL_CORS_ORIGIN in the service's environment. Allow ports 80 and 443 through Windows Defender Firewall for a public endpoint, or only the chosen port for a local network. The repository's checks of the reference server run on Linux; Windows is not covered by CI.
| Request | Answer |
|---|---|
GET or HEAD of <pack>/pack.vglm or *.vgls
|
The file, as application/octet-stream, with ranges |
Range: bytes=a-b or bytes=a-
|
206 Partial Content |
| A range past the end of the file | 416 |
Several ranges in one request (bytes=0-1,4-5) |
416: vgl never asks for them, and they let a client make the server do much work for one small request |
If-None-Match with the current ETag
|
304 Not Modified |
OPTIONS (a CORS preflight) |
204 with the CORS headers |
Any other method (POST, PUT, DELETE, ...) |
405, with Allow: GET, HEAD, OPTIONS
|
Directories, directory listings, other files, hidden files (any path with /.), paths outside the pack directory (.., encoded or not), missing files |
404 |
It never compresses responses, and it does not name itself in a Server header.
On every response:
| Header | Value | Why |
|---|---|---|
Access-Control-Allow-Origin |
VGL_CORS_ORIGIN (*) |
Browsers on other sites may read packs |
Access-Control-Expose-Headers |
Accept-Ranges, Content-Length, Content-Range, ETag |
Scripts may see the range headers |
Cross-Origin-Resource-Policy |
cross-origin |
Cross-origin isolated pages, as vgl's web build is, may load packs however they ask |
X-Content-Type-Options |
nosniff |
Browsers do not guess content types |
Referrer-Policy |
no-referrer |
|
Strict-Transport-Security |
max-age=31536000 |
Over HTTPS only |
On pack files:
| File | Cache-Control |
Why |
|---|---|---|
*/pack.vglm |
public, max-age=60 |
A manifest changes in place when a pack is rebaked, so caches check it again after a minute; the ETag makes the recheck a 304
|
*.vgls |
public, max-age=31536000, immutable |
A shard's name holds a digest of its content, so caches may keep it forever |
On preflight requests (OPTIONS): Access-Control-Allow-Methods: GET, HEAD, OPTIONS, Access-Control-Allow-Headers: Range, If-None-Match, If-Range and Access-Control-Max-Age: 86400.
The server also limits slow clients: 10 seconds to read request headers and bodies, 2 minutes to write a response, 2 minutes for idle connections, and 16 KB of request headers.
tools/server/check.sh checks the configuration end to end. It needs bash, curl, a Caddy binary and the headless sample built with VGL_DEFAULTS_HTTP:
tools/server/check.sh "$(command -v caddy)" build/linux-gcc-shared/bin/Release/vgl-headless [port]The port defaults to 18080. The script:
- Copies the sample packs from
samples/data/packsto a temporary directory, and plants files the server must not hand out next to them: a hidden.hidden.vgls, anotes.txtinside a pack, and anoutside.vglsoutside the pack directory. - Starts Caddy with the Caddyfile on that directory.
- Checks what a pack reader needs:
200for the manifest (GETandHEAD);206forbytes=0-15andbytes=16-;416past the end; the exactContent-RangeandContent-Length;Accept-Ranges: bytes; the content type,nosniff, bothCache-Controlvalues,Access-Control-Allow-Origin: *andCross-Origin-Resource-Policy; anETagand a304for it; a204preflight with the allowed headers; and noServerheader. - Checks what it refuses:
405forPOST,PUTandDELETE;416for several ranges;404for the root, a pack directory with and without a slash, the planted files, and path traversal attempts (../,%2e%2e,..%2f), and for a missing shard. - Loads the sample packs (GEBCO, GLO-30 Sognefjord, EGM2008, Blue Marble) with the headless sample, once from disk and once over HTTP from the server, and requires the heights it prints at its places to be identical.
It prints The reference server passed its checks. and exits with 0, or lists each failure with the end of Caddy's log and exits with 1. CI runs it on Linux with Caddy 2.11.7, downloaded at a pinned checksum, after the linux-gcc-shared build (.github/workflows/ci.yml).
For your own deployment, vgl-bake check <url> performs the client-side checks against any host, including one running this configuration (Hosting packs).
The configuration covers what a web server can enforce on its own. Also:
- Limit request rates. Caddy has no rate limiting without plugins. Put a CDN in front (it caches shards at its edge and absorbs floods), or limit connections per address in the firewall.
- Keep Caddy updated, and the operating system with it. Watch Caddy's releases for security fixes.
- Give Caddy read-only access to the packs, and run it as an unprivileged user. Caddy follows symbolic links, so keep links that point outside the pack directory out of it.
- Open only ports 80 and 443 (and UDP 443 for HTTP/3).
-
Mind the logs. Access logs hold IP addresses, which are personal data in the EU. Keep them only as long as needed, or remove the
logblock. -
Publish the attribution that each dataset's license requires; the runtime reads it from the manifests (
vgl_context_attribution_get), so applications can show it.
The Caddyfile is short and commented; copy it and change what you need. Common changes:
-
Restrict browser origins. Set
VGL_CORS_ORIGIN=https://app.example.org. The variable takes one origin, sent as it is; for several origins, echo the request'sOriginwhen it is on an allow list (with a matcher on theOriginheader andVary: Origin), or set the rule at a CDN. -
Different cache lifetimes. Change the
Cache-Controlof the@manifestmatcher (keep it short) or the@shardmatcher (keep it long andimmutable; shards never change). -
Serve packs under a path prefix, next to other content on the same site. Wrap the pack routes in
handle_path /packs/* { ... }, as samples/gl/web/Caddyfile does for/data/packs/*, and keep therespond 404fallback inside it. -
Serve a web application from the same site. Follow the web sample's Caddyfile: it adds
Cross-Origin-Opener-Policy: same-originandCross-Origin-Embedder-Policy: require-corpfor the page, which the web build needs for threads (Web builds). -
No logs, or logs to a file. Remove the
logblock, or change itsoutputtofile <path>. -
Behind a CDN or reverse proxy. Set
VGL_SITEto a port such as:8080. Caddy then serves plain HTTP and does not sendStrict-Transport-Security, so set it at the CDN. - Rate limiting. Add a rate-limiting plugin to a custom Caddy build, or do it in the firewall or CDN.
Whatever you change, keep these properties, which vgl relies on: single byte ranges with 206, no compression of pack files, application/octet-stream, CORS for Range if browsers read the packs, and long caching only for shards. Then run tools/server/check.sh (for the configuration) and vgl-bake check (for the deployment) again.
See also Hosting packs, Pack format overview and Baker.
Getting vgl
Using the library
Rendering
Data
Reference
Specifications