This is the continuation of the previous post about a Battlesnake server. This one will talk about what the deploy process looks like and some Opentelemetry data collected from it.

Digitalocean App Platform with Dockerfile

I’ve been a Digitalocean customer/user for a while now so I gave their App Platform PaaS a try. They use buildpacks or detect a Dockerfile which will build and run your app, and I went with the latter (not sure if the Crystal buildpack is supported somewhow).

Michael Nikitochkin’s article taught me ~95% of what I needed to come up with an efficient container deployment with a small footprint image (alpine).

# Build image
FROM crystallang/crystal:1.7.2-alpine as builder
# Cache dependencies
COPY ./shard.yml ./shard.lock /opt/
RUN shards install -v
# Build a binary
COPY . /opt/
RUN crystal build --static --release ./src/
# ===============
# Result image with one layer
FROM alpine:latest
COPY --from=builder /opt/app .
ENTRYPOINT ["./app", "-p", "8080"]

The app is deployed with every commit to main and cached layers are nice. The entire process takes ~2min40s on average when a docker image needs to be built (with cached dependencies) and when the deploy was triggered by a config update (i.e. ENV variable update) it takes under 2min on averge.


To understand what’s going on I added Opentelemetry tracing with Some cool stats from a few days of ranking on the leaderboards:

honeycomb opentelemetry stats

You can tell leaderboards run on specific hours and the server sits there without much activity about half the time. My favorite stat there is P99 on ~3.1 milliseconds with all that logic running (from previous post).

Lots of responses manage to respond in microseconds though, as seen in the runtime logs (below) and the P50 stat on Honeycomb (above):

microsecond response times

All in all pretty sweet ⚡️

More about other learnings from this project to come soon.

Pura vida.