Jig [ jig ]

noun

a device that holds a piece of work and guides the tool operating on it.

What is Jig?


Jig is a dead simple deployment tool to automate routine work with Docker, Docker Compose, and Traefik to streamline running services on your own virtual servers with the following goals:

  • Bring Vercel DX to own servers: Vercel is setting a standard for deployment tools for many years and aiming for anything less would mean disservice to everyone
  • Minimize error human error via automation: It's very easy to forget one little command, skip a stop while removing a container and you get an error, start over and get sad. Automation solves this, but when bash scripts aren't enough stuff like Jig should be a great start
  • Keep things fast: From one line deployments to keeping disk writes to absolute minimum streaming data whereever this is possible, it is important to keep things fast for the comfortable blazingly fast™ automation
  • Stay practical: Single-container apps stay simple, while compose deployments let one upload describe a whole stack and selectively expose only the services that should be routable

Jig was heavily inspired by Zeit (older version of Vercel) and Exoframe.js with focus on self-hosting and being minimal whenever this is possible.

Installation


Installation is in two steps:

  • Server setup: Run a startup script on the server to pull all relevant images and take off
  • Client setup: Download a client and get authentication ready

Server setup

Docker is a prerequisite, so ensure docker is available and running on your server

DNS management is not builtin just yet so you need to point domain names you wish to use with Jig manually. 😔

Thankfully it's mostly a one-time thing and you won't need to deal with it later. With Vercel domains it's as easy as an example below, but it depends on your provider

bash
vc dns add <your base domain> <sub domain> A <your server public IP>

As a last infra part you'll need to open up ports 80 (http) and 443 (https). Traefik handles https redirection and Let's Encrypt http challenges so you don't need to worry about unsecured connections

Load and run startup\update script below

bash
curl -fsSLO https://deploywithjig.askh.at/init.sh && bash init.sh

This will load Traefik and Jig, then ask whether the Jig control plane should be exposed publicly or only through Tailscale. The script launches everything and prints the jig login command to run on your machine

If Tailscale is installed on the server, the bootstrap flow defaults to a private control plane and configures tailscale serve for the Jig API. Your deployed apps can still stay public through Traefik

Login command will look something like jig login loooooong+code keep it for later

Client setup

bash
curl -fsSL https://deploywithjig.askh.at/install.sh | bash

After that just plug in the command you received in Server setup stage and start deploying

Start deploying


Initiate a Jig project and create the config

bash
jig init

Use jig init in any project directory to create the starting config. For a classic single-container app, add a Dockerfile and a jig.json. Jig will pack the project, send it to the server, and build it remotely

bash
jig deploy

Use jig ls to check that it is running, then jig logs <name> or jig stats to inspect the live deployment

For compose stacks, jig ls shows one stack with child services underneath it. Target a child with stack:service, for example jig deployments logs my-stack:api

If the project contains docker-compose.yaml, docker-compose.yml, compose.yaml, or compose.yml, Jig switches to compose mode and deploys the stack with docker compose

json
{
  "name": "my-stack",
  "composeFile": "docker-compose.yaml",
  "restartPolicy": "unless-stopped"
}

For legacy compose projects you can point Jig at the primary routed service with composeService. For newer setups, add x-jig blocks on the services you want Jig to expose. Services without x-jig stay internal, which is how you keep things like databases in the stack without giving them a public route

yaml
services:
  frontend:
    build: .
    x-jig:
      name: frontend
      domain: app.example.com

  api:
    build: ./api
    x-jig:
      name: api
      domain: api.example.com
    environment:
      # Pull this from a server-side Jig secret named "database-url"
      DATABASE_URL: "@database-url"

  db:
    image: postgres:16
    # No x-jig block here means this service stays internal to the compose stack

Secret values in envs use the same @secret-name convention, so a service can consume the same server-side secret whether it is deployed alone or through compose

Local image deploys via jig deploy -l still work for single-container projects, but are not supported for compose deployments

bash
jig deploy -l

Delete a deployment with jig rm <name>. Compose stacks use the same command shape for child services, jig rm my-stack:api. Rollback is available for single-container deployments. Compose deployments are listed and deletable, but rollback is intentionally disabled

Let Traefik fetch certificates if you deploy with TLS enabled and you're done