← Blog

Deploying a Go service: the build that doesn't need a runtime

Railyard

October 10, 2026

A Go build produces one statically linked binary, with nothing left to resolve at runtime — no interpreter version to pin, no gems or packages to fetch on the server that’s about to run the app. That’s a genuinely different thing to build than most interpreted-language apps, and it shows up as fewer places for a deploy to go wrong: once the binary compiles, it runs, full stop.

What gets detected

  • Go itself is detected from go.mod — including a module that lives in a subfolder (server/go.mod next to a frontend/ directory), found and built from the right place automatically.
  • A known database driver import — github.com/jackc/pgx, github.com/lib/pq, github.com/go-sql-driver/mysql, github.com/redis/go-redis — provisions the matching database and injects DATABASE_URL or REDIS_URL, with no addon to add by hand first.
  • The build runs through Railpack by default, or your own Dockerfile if the repo has one.
railyard create my-app https://github.com/me/my-app --port 8080

or the same URL from the dashboard once GitHub’s connected. The one requirement worth stating plainly: the binary has to listen on $PORT, and on all interfaces, not just localhost.

Workers, since Go has no framework convention to detect

Unlike Rails or Django, Go doesn’t have one dominant job-queue library to look for — there’s nothing to auto-offer as a worker. That’s fine, because it’s one line either way:

web:    ./server
worker: ./server -mode=worker

Both lines run the exact same built binary; main() reads a flag or env var to decide which loop to actually run. One build, several process types, each scaled independently:

railyard scale my-app worker=2

For anything that runs on a schedule rather than continuously, Cron runs the binary with a different flag at the scheduled time and keeps a run history — a cleaner model than a for { time.Sleep(...) } loop baked into the worker process.

Config and secrets

Everything read with os.Getenv is a plain env var under Variables, or set with railyard env:set my-app KEY=VALUE — never compiled into the binary, so the exact same build artifact runs unchanged from staging through production. No recompile per environment, which is the point: a config difference between staging and prod should be a variable, never a different build.

Building your own image, when you want to

railyard deploy my-app --local builds with your own Docker and pushes a single image directly, instead of using the shared team builder — useful when a build needs something specific to your local toolchain that the standard build path doesn’t need to know about.

Full reference: Go docs, Workers and scheduled jobs.

Push a Go service right now.

go.mod is read automatically — database driver detection, $PORT binding, workers via Procfile.

Free plan, no card, and 14 days of Pro for every new team. One flat price per team.