ShipIt

Inspiration

Deploying a small Docker Compose app to a cloud server is rarely just docker compose up. Someone still has to create the server, manage SSH access, point DNS at it, put HTTPS in front of it, and recover when a release fails.

ShipIt came from wanting that path to be explicit and repeatable for a single application without hiding the operational responsibilities behind a dashboard.

What it does

ShipIt is a CLI for deploying a committed Docker Compose application to its own DigitalOcean Droplet.

It guides developers through configuration, provisions the Droplet and firewall with Terraform, bootstraps Docker and a restricted deployment user over SSH, then deploys a clean Git revision. Caddy is the only public listener, obtains HTTPS certificates, and proxies traffic to the app’s declared healthy service.

The workflow includes init, provision, deploy, status, rollback, and destroy. Deployments use a remote lock, and a failed health check restores the previous containers and Caddy configuration when a prior revision exists.

How we built it

We built ShipIt in TypeScript around a CLI and an application-lifecycle layer, with Terraform generating and owning the DigitalOcean infrastructure.

The deployment path transfers a committed revision and an untracked environment file over SSH, starts the Compose application, waits for the declared public-service health check, then configures Caddy and verifies HTTPS.

We kept configuration versioned and non-secret in shipit.yaml, while provider tokens, private SSH keys, Terraform state, and .env.shipit remain local. The project is tested at the CLI, configuration, infrastructure, and lifecycle boundaries.

How we used Codex

We used Codex with GPT-5.6 from design through verification.

Codex helped us turn the deployment workflow into focused TypeScript, Terraform, and test changes. It was especially useful for tracing behavior across the CLI, local infrastructure state, and remote host; iterating on tests; tightening documentation; and finding edge cases in the release flow.

The goal was not to hand over design decisions. We kept the important safety rules visible and testable: deploy only a clean committed revision, assign infrastructure ownership to the Terraform state owner, expose applications only through Caddy, gate releases on a health check, serialize mutating operations, and retain earlier revisions for rollback.

Challenges we ran into

The hard part was not starting containers; it was defining safe ownership and failure behavior.

Terraform state belongs to one designated state owner, because that person can safely provision or destroy the server. Deployments must use clean, committed source so an untracked local edit cannot quietly reach production.

The Compose contract also needs to stay narrow: no host ports, no host networking, one declared public service with a health check, and Caddy as the only public ingress.

Rollback is deliberately limited. ShipIt can restore a previous container and proxy configuration, but it cannot undo database changes or recover deleted persistent data. Destroying a Droplet is intentionally explicit because ShipIt does not create backups.

Accomplishments that we're proud of

We turned a chain of manual infrastructure and release steps into a small command surface with visible safety boundaries.

ShipIt:

  • Refuses to deploy uncommitted work
  • Separates provisioning from deployment
  • Prevents concurrent mutating operations with a remote lock
  • Checks application health before marking a release current
  • Keeps five prior revisions for container and configuration rollback
  • Makes the limits clear: it is a single-server deployment tool, not a Kubernetes replacement, backup system, or zero-downtime guarantee

What we learned

Good deployment tooling should make responsibility clearer, not merely make commands shorter.

A healthy-service check is useful, but it does not prove a migration is reversible. HTTPS automation is valuable, but it does not replace DNS ownership. And rollback only earns trust when its limits are explicit.

We also learned that source control, infrastructure state, SSH access, network exposure, and application health have to be treated as one workflow. Leaving any of them implicit is where simple deployments become hard to debug.

What's next for Shipit

ShipIt’s next direction is a public, npm-distributed CLI with a clean public repository and support for AWS alongside DigitalOcean.

The project also plans protected cloud smoke tests that provision a disposable application, deploy it, verify health, roll it back, destroy it, and confirm cleanup.

Built With

Share this project:

Updates