CI/CD for a Crystal App with GitHub Actions
Install shards, run crystal spec, check formatting, and build a release binary on every push.
This recipe builds a Crystal app using the official Crystal container image. CI installs shards, runs the spec suite and a formatting check, then compiles a release binary for deployment.
What the pipeline does
- run inside the official crystal container
- install dependencies with shards install
- check formatting with crystal tool format --check
- run specs with crystal spec
- build a release binary
The workflow
Running in the crystallang/crystal container guarantees the compiler and stdlib match. shards install resolves dependencies; crystal build --release produces an optimized binary.
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
container: crystallang/crystal:1.12
steps:
- uses: actions/checkout@v4
- run: shards install
- run: crystal tool format --check
- run: crystal spec
- run: crystal build src/app.cr --release -o bin/app
- uses: actions/upload-artifact@v4
with:
name: crystal-bin
path: bin/appCaching and speed
Cache the lib directory with actions/cache keyed on shard.lock to skip re-resolving shards. Crystal release compilation is single-threaded and slow; cheaper managed runners such as Latchkey (around 69% cheaper than GitHub-hosted) cut compile time and auto-retry a flaky shards fetch.
Release output
The --release binary is statically-ish linked and the deploy artifact. For tagged releases, attach bin/app to a GitHub Release; for containers, copy the binary into a slim runtime image in a follow-up deploy job.
Key takeaways
- Run in the crystallang/crystal container so the toolchain is pinned.
- Add crystal tool format --check so formatting drift fails CI.
- Cache the lib directory keyed on shard.lock to speed installs.