goci is a lightweight CI/CD runner that deploys your services without SSH keys. It listens for GitHub webhook events, runs a configurable pipeline (pull image, redeploy container, run commands), and reports the result back to GitHub as a commit status. Your GitHub Actions workflow waits for the status and finishes accordingly — giving you a clean, auditable deployment gate directly inside your PR/push workflow.
Key features:
- No SSH keys or VPN required on the target host
- Driven by GitHub webhooks (
workflow_joborpushevents) - Commit status integration — GitHub Actions waits for goci to finish
- Per-service pipeline configuration (JSON or YAML)
- Docker Compose-aware: pulls new images and redeploys services
GITHUB GOCI
──────────────────────────────────────────────────────────────────────
git push
│
▼
Actions: build/push job
├─ build container image
└─ push image to registry
│
│ workflow_job.completed
│ ──────────────────────────────────────────▶ receive webhook
│ │
│ [1] set commit status: pending
│ │
Actions: deploy job [2] run pipeline
(waiting for commit status) ├─ pull new image
│ ├─ redeploy service
│ └─ run steps
│ │
│ [3] set commit status
│ ┌──────┴────────┐
│ success failure
│ │ │
│◀───────────────── commit status: success ──┘ │
│◀───────────────────────────── commit status: failure ──────┘
│
▼
deploy job: ✓ success / ✗ failure
These are global defaults. Any value can be overridden per-service in the config file.
| Variable | Default | Description |
|---|---|---|
GOCI_HOST |
(empty) | Optional. Public URL of this goci instance. Used in status update descriptions. |
GOCI_SERVER_ADDR |
:7878 |
Address and port for the goci HTTP server. |
GOCI_LOG_LEVEL |
info |
Log verbosity: debug, info, warn, error. |
GOCI_GITHUB_TOKEN |
(required) | GitHub personal access token (or GITHUB_TOKEN from Actions). Used to set commit statuses. |
GOCI_GITHUB_WEBHOOK_PATH_PREFIX |
(empty) | Optional. Path prefix applied to all service webhook URLs. When set, the effective webhook path for each service becomes <prefix>/<service-name>, overriding the path field in the service config. Trailing slashes are stripped automatically. |
GOCI_GITHUB_WEBHOOK_SECRET |
(empty) | Secret used to validate incoming GitHub webhooks. |
GOCI_GITHUB_METHOD |
POST |
HTTP method expected for the webhook endpoint. |
GOCI_GITHUB_RESPONSE_TIMEOUT |
10s |
Timeout for outbound GitHub API calls. |
GOCI_GITHUB_TARGET_BRANCH |
main |
Only process events targeting this branch. |
GOCI_GITHUB_EVENT_TYPE |
push |
GitHub event type to listen for. Use workflow_job for workflow-driven deployments. |
GOCI_GITHUB_COMMIT_STATUS_CONTEXT |
goci/pipeline |
Context string for GitHub commit status updates. Must match the context field in your GitHub Actions wait-commit-status step. |
GOCI_GITHUB_WORKFLOW_NAME |
(empty) | Optional. Filter by workflow name (the name: field at the root of the workflow YAML). |
GOCI_GITHUB_WORKFLOW_JOB_NAME |
(required for workflow_job) |
Name of the GitHub Actions job whose completion triggers goci. |
GOCI_GITHUB_WORKFLOW_ACTION |
completed |
Workflow job action to react to: queued, in_progress, or completed. |
See .env.example for a ready-to-copy template.
Pass the path with -config /path/to/config.yaml (default: config.json).
Defines one or more services, each with its own pipeline and GitHub webhook settings. Service-level GitHub fields override the global environment variables.
YAML example:
- name: "my-service"
pipeline:
jobs:
- name: "deploy"
steps:
- name: "pull image"
cmd: "docker"
args: ["compose", "pull", "my-service"]
dir: "/app"
timeout: 60s
- name: "restart service"
cmd: "docker"
args: ["compose", "up", "-d", "my-service"]
dir: "/app"
timeout: 30s
github:
token: "<github-token>" # overrides GOCI_GITHUB_TOKEN
webhook:
path: "/webhook/my-service" # GitHub webhook URL path
method: "POST" # optional, default POST
secret: "<webhook-secret>" # overrides GOCI_GITHUB_WEBHOOK_SECRET
branch: "main" # target branch filter
event: "workflow_job" # event type
commit_status_context: "goci/deploy" # overrides GOCI_GITHUB_COMMIT_STATUS_CONTEXT
workflow:
action: "completed"
job_name: "build-and-push" # GitHub Actions job name that triggers gociJSON example: see config.json.example.
# Docker Hub
docker pull 8bitdogs/goci:latest
# GitHub Container Registry
docker pull ghcr.io/8bitdogs/goci:latestAdd goci as a service in your docker-compose.yaml:
name: "<compose-name>" # required
services:
# your application services...
goci:
image: 8bitdogs/goci:latest
container_name: goci
depends_on:
- your-service
env_file:
- .env # top-level configuration
volumes:
- ~/.docker/config.json:/root/.docker/config.json:ro # registry auth
- /var/run/docker.sock:/var/run/docker.sock # Docker control
- ./docker-compose.yaml:/docker-compose.yaml # Compose file for redeployment
- ./config.yaml:/config.yaml # goci service config
command: goci -config /config.yamlNote: Mounting
docker.sockgives goci the ability to pull images and restart containers on the host. Ensure the goci container is trusted.
Add a deploy job to your workflow that waits for goci to report the deployment result via commit status:
jobs:
docker:
name: build and push docker image
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and push
# ... your build/push steps
deploy:
name: deploy
needs: docker
runs-on: ubuntu-latest
permissions:
statuses: read # wait-commit-status polls the commit status
steps:
- uses: 8bitdogs/wait-commit-status@main
with:
token: ${{ secrets.GITHUB_TOKEN }}
context: goci/deploy # must match GOCI_GITHUB_COMMIT_STATUS_CONTEXT or service config
interval: 5 # poll every 5 seconds
timeout: 300 # fail after 5 minutesThe deploy job will block until goci sets the commit status to success or failure, ensuring your workflow reflects the actual deployment result.
The only permission required for goci integration is:
| Permission | Job | Why |
|---|---|---|
statuses: read |
deploy |
wait-commit-status polls commit statuses until goci responds |
goci writes commit statuses using its own
GOCI_GITHUB_TOKEN(a PAT configured on the server), not the workflow'sGITHUB_TOKEN. The workflow only needsstatuses: readsowait-commit-statuscan poll for the result.
The
contextvalue must exactly matchGOCI_GITHUB_COMMIT_STATUS_CONTEXT(env) orcommit_status_contextin your service config.
Go to your repository (or organization) → Settings → Webhooks → Add webhook.
Combine your goci host with the webhook path defined in your service config:
<CI_HOST> + <github.path from service config>
# example
https://goci.example.com/webhook/my-service
Select application/json.
Generate a high-entropy secret and paste it into the Secret field. Use the same value for GOCI_GITHUB_WEBHOOK_SECRET (env) or secret in your service config.
openssl rand -hex 32Choose "Let me select individual events" and enable:
| Event | Purpose |
|---|---|
| Ping | Verifies the webhook is reachable (always enable) |
| Push | Triggers goci on push events (GOCI_GITHUB_EVENT_TYPE=push) |
| Workflow jobs | Triggers goci when a workflow job completes (GOCI_GITHUB_EVENT_TYPE=workflow_job) |
Disable all other events to reduce noise.
After saving, GitHub immediately sends a ping event. Open the Recent Deliveries tab and check the response code:
| Status | Meaning |
|---|---|
200 |
Webhook validated and pipeline executed |
202 |
Webhook validated but nothing matched (no pipeline executed) |
4xx / 5xx |
Error — see the response payload message for details |
Any other status or a failed delivery means goci is unreachable or the payload was rejected — check that goci is running, the Payload URL is correct, and the secret matches.
# 1. Copy and fill in environment config
cp .env.example .env
# 2. Create your service config
cp config.yaml.example config.yaml
# 3. Run with Docker Compose
docker compose up -d gociOr build from source:
make up