Automatically deploy your websites and applications when you push code to GitHub!
This tool watches your GitHub repositories and automatically deploys your code to your server whenever you make changes. No more manual deployments - just push your code and it goes live!
Imagine you have a website or app on GitHub. Every time you make changes and push them to GitHub, this tool will:
- Notice the changes - Gets notified instantly when you push code
- Download the latest code - Pulls your changes to your server
- Build your project - Runs commands like installing dependencies and building
- Deploy it live - Restarts your website/app with the new code
- Tell you what happened - Logs everything with detailed status information
- Instant Deployments: Your changes go live seconds after you push to GitHub
- Secure: Only deploys when GitHub sends the correct secret key
- Safe: You can implement your own rollback strategies in your deployment commands or scripts
- Multiple Projects: Handle many websites/apps from one tool
- Smart: Only deploys from specific branches (like
main) - Transparent: See exactly what's happening with detailed logs
- Web Log Viewer: Real-time log monitoring with project identification and dark theme
- Flexible Configuration: TOML-based config with multiple location support
1. You push code to GitHub
↓
2. GitHub sends a notification to this tool
↓
3. Tool downloads your latest code
↓
4. Tool runs your build commands (install, build, etc.)
↓
5. Tool restarts your website/app
↓
6. Your changes are now live!
If something goes wrong: Future versions will support built-in rollback strategies. For now, handle rollback manually in your deployment commands or scripts.
Option A: Install with go install (Recommended - Easiest)
# Install directly from GitHub (no cloning needed)
go install github.com/ktappdev/cicd-thing@latest
# Make sure Go's bin directory is in your PATH
export PATH=$PATH:$(go env GOPATH)/bin
# Run the application
cicd-thingOption B: Install from Source
# Download the code
git clone https://github.com/ktappdev/cicd-thing.git
cd cicd-thing
# Install dependencies
go mod tidy
# Build the application
go build -o cicd-thing .
# Run the application
./cicd-thingThe application uses TOML configuration files and will automatically create one for you!
# Run it once to create the default config
cicd-thing
# or if building from source: ./cicd-thingThe application will create a config.toml file in ~/.config/cicd-thing/config.toml with a configuration marker and show you what needs to be configured:
=== CONFIGURATION REQUIRED ===
A default configuration file has been created at: ~/.config/cicd-thing/config.toml
Please edit this file with your settings before running the application again.
Required fields to configure:
- webhook_secret: Your GitHub webhook secret
- api_key: Your API key for authentication
- repositories: Map of repository names to local paths
===============================
# Edit the config file with your settings
nano ~/.config/cicd-thing/config.toml # or use any text editorIMPORTANT: After configuring your settings, remove this line from the config file:
# REMOVE THIS LINE AFTER CONFIGURATION: # CONFIGURATION_NEEDEDThe application will not start until this marker line is removed - this ensures you don't accidentally run with the default placeholder values!
If you try to run the application again without removing the marker line, you'll see:
=== CONFIGURATION REQUIRED ===
Configuration file found but not configured: ~/.config/cicd-thing/config.toml
Please edit this file with your settings before running the application again.
IMPORTANT: Remove the line '# CONFIGURATION_NEEDED' after configuring!
Required fields to configure:
- webhook_secret: Your GitHub webhook secret
- api_key: Your API key for authentication
- repositories: Map of repository names to local paths
===============================
This prevents you from accidentally running with placeholder values!
# Run the application
cicd-thing
# or if building from source: ./cicd-thing- Go to your GitHub repository
- Click Settings → Webhooks → Add webhook
- Set Payload URL to:
http://your-server:3000/webhook - Set Content type to:
application/json - Set Secret to the same value as
webhook_secretin your config.toml file - Select Just the push event
- Click Add webhook
That's it! Now when you push code to GitHub, it will automatically deploy!
The application searches for config.toml in these locations (in order):
~/.config/cicd-thing/config.toml(primary per-user config)/etc/cicd-thing/config.toml(system-wide config)/usr/local/etc/cicd-thing/config.toml(alternative system config)./config.toml(legacy current directory support)./config/config.toml(legacy local config directory support)
If no configuration file is found, the application will:
- Create a comprehensive default
config.tomlat~/.config/cicd-thing/config.toml - Include helpful comments and examples for all options
- Add a configuration marker (
# CONFIGURATION_NEEDED) to prevent accidental startup - Clearly mark required vs optional settings
- Exit with instructions for you to configure it
Configuration Marker System: The created config file includes a special marker that must be removed after configuration. This prevents the application from starting with placeholder values and ensures you review all required settings.
Your config.toml file contains all the settings. Here's what each section means:
# Server settings
port = "3000"
webhook_secret = "YOUR_WEBHOOK_SECRET_HERE" # REQUIRED
api_key = "YOUR_API_KEY_HERE" # REQUIRED
# Repository mappings - REQUIRED (use full GitHub repo names)
[repositories]
"johndoe/my-app" = "/var/www/my-app"
"company/api-service" = "/opt/api-service"| Setting | What It Does | Example |
|---|---|---|
webhook_secret |
Secret password GitHub uses to verify it's really GitHub calling | abc123secret456 |
api_key |
Password for manually triggering deployments (not needed for log viewing) | myapikey789 |
repositories |
Which GitHub repos go to which folders on your server | See examples below |
Understanding webhook_secret vs api_key:
webhook_secret- Used by GitHub webhooks. GitHub signs every webhook request with this secret (HMAC-SHA256). The tool verifies the signature to ensure the request is really from GitHub. You'll use this for normal push → deploy flow.api_key- Used for manual deployments via the/deployendpoint. Allows you to trigger deployments without pushing to GitHub (e.g., re-deploy current code, deploy from different branch, integrate with other CI/CD tools). Passed asAuthorization: Bearer <api_key>header.
When you use each:
- Normal workflow (push to GitHub → auto-deploy): Uses
webhook_secretonly - Manual deployment via API: Uses
api_key - Viewing logs/health/status: No authentication required
# Logging
log_file = "./cicd-thing.log"
max_log_size_mb = 10 # Rotate log when it reaches this size (MB)
max_rotated_logs = 5 # Keep this many rotated log files
# Default commands to run for deployments
default_commands = "git pull && npm ci && npm run build"
# Branch filtering
# Global default when no per-app override is set
branch_filter = "main"
# Optional per-app branch filters (keys match app names used in [commands])
[branch_filters]
# "my-app" = "main"
# "api-service" = "release"
# Performance settings
concurrency_limit = 2
timeout_seconds = 300
# Features
dry_run = false
| Setting | What It Does | Default | Example |
|---|---|---|---|
log_file |
Path to the log file | ./cicd-thing.log |
/var/log/cicd-thing.log |
max_log_size_mb |
Rotate log when it reaches this size (MB) | 10 |
50 |
max_rotated_logs |
Number of rotated log files to keep | 5 |
10 |
port |
What port the tool runs on | 3000 |
8080 |
branch_filter |
Only deploy from this branch | main |
production |
timeout_seconds |
How long to wait before giving up | 300 (5 minutes) |
600 |
dry_run |
Test mode (doesn't actually deploy) | false |
true |
Tell the tool which GitHub repository goes to which folder on your server:
[repositories]
"myorg/web" = "/var/www/web"
"myorg/api" = "/opt/api"Optionally, define stable app names once per repo to avoid repeating names everywhere:
[app_names]
"myorg/web" = "web"
"myorg/api" = "api"Tell the tool what commands to run when deploying each project (keys are app names):
[commands]
"web" = "git pull && npm ci && npm run build && pm2 restart web"
"api" = "git pull && go build -o api . && systemctl restart api"Upcoming versions will provide first-class rollback support (tracking previous deployment state and enabling safe, automated rollbacks). For now, implement any rollback logic directly in your own deployment tooling or scripts.
- Built-in version tracking to automatically capture previous deployment state.
- First-class "one-click" rollback to the last known good deployment.
- Getting Started Guide - Step-by-step setup for beginners
- FAQ - Common questions and answers
- API Documentation - Technical API reference
- Deployment Examples - Real-world configuration examples
The tool provides several web endpoints you can use:
- What it does: Receives webhook notifications from GitHub when you push code
- Who uses it: GitHub automatically calls this when you push code
- You don't need to worry about this - it's automatic!
- What it does: Lets you trigger a deployment manually
- How to use:
curl -X POST "http://your-server:3000/deploy?repo=username/repository" \ -H "Authorization: Bearer your-api-key"
- When to use: When you want to deploy without pushing to GitHub
- What it does: Shows if the tool is running properly
- How to use: Visit
http://your-server:3000/healthin your browser (default path) - What you'll see: Information about the tool's status and configuration
- Path is configurable via
health_pathin config.toml
- What it does: Shows current deployment status and configuration
- How to use: Visit
http://your-server:3000/statusin your browser (default path) - What you'll see: List of configured repositories and deployment settings
- Path is configurable via
status_pathin config.toml
- What it does: Displays real-time deployment and system logs with project identification
- How to use: Visit
http://your-server:3000/logs?limit=50in your browser - Rate limiting: Limited to 30 requests per minute per IP address for optimal performance
- What you'll see: Project-prefixed logs with configurable line limits and auto-refresh
- Push to GitHub → Webhook triggered → Deployment executed
- Manual deployment via API
- (Planned) Automated rollback support based on tracked deployment history
Node.js Application:
[commands]
"myapp" = "git pull && npm ci && npm run build && pm2 restart myapp"Go Application:
[commands]
"api" = "git pull && go build -o api . && systemctl restart api"Docker Application:
[commands]
"webapp" = "git pull && docker build -t webapp . && docker-compose up -d"Static Website:
[commands]
"website" = "git pull && npm run build && rsync -av dist/ /var/www/html/"-
Generate webhook secret:
openssl rand -hex 20
-
Generate API key:
openssl rand -hex 32
-
(Recommended) Protect the service with a reverse proxy or firewall
- Restrict who can reach
/webhook,/deploy, and/logsat the network edge (e.g. Nginx, Caddy, Cloudflare, firewalls). - See Network Security section below.
- Restrict who can reach
Option A: Install with go install (Recommended)
# Install globally
go install github.com/ktappdev/cicd-thing@latest
# The binary will be available in ~/go/bin/ or add to PATH:
sudo cp ~/go/bin/cicd-thing /usr/local/bin/Option B: Build from source
# Clone and build
git clone https://github.com/ktappdev/cicd-thing.git
cd cicd-thing
go build -o cicd-thing .
# Install to system location
sudo cp cicd-thing /usr/local/bin/-
Create system config directory:
sudo mkdir -p /etc/cicd-thing sudo cp config.toml.example /etc/cicd-thing/config.toml
-
Configure for production:
sudo nano /etc/cicd-thing/config.toml
-
Create systemd service (optional):
sudo tee /etc/systemd/system/cicd-thing.service > /dev/null <<EOF [Unit] Description=CICD-Thing Deployment Orchestrator After=network.target [Service] Type=simple User=deploy ExecStart=/usr/local/bin/cicd-thing WorkingDirectory=/home/deploy/.config/cicd-thing Environment=HOME=/home/deploy Restart=always RestartSec=5 [Install] WantedBy=multi-user.target EOF sudo systemctl enable cicd-thing sudo systemctl start cicd-thing
Important systemd settings:
WorkingDirectory- Sets where the app runs from (affects relative log paths)Environment=HOME=/home/deploy- Required! Without this, the app can't find~/.config/cicd-thing/config.tomlbecause systemd doesn't set HOME by default- Change
/home/deployto match your user's home directory - If using
User=root, setEnvironment=HOME=/root
CICD-Thing relies on a combination of mechanisms for secure operation:
- GitHub webhook HMAC verification for
/webhookrequests (usingwebhook_secret). - API key authentication for
/deployrequests (usingapi_key). - Rate limiting on
/logsto prevent abuse. - External network controls (reverse proxies, firewalls, WAFs, security groups, etc.) to restrict who can hit the service.
We intentionally do not include a built-in IP allowlist. If you require IP-based restrictions, enforce them at the network edge.
All deployment events are logged to the configured log file and stdout:
2025-06-24T10:15:00Z | Hello-World | main | 1481a2de | STARTED
2025-06-24T10:15:10Z | Hello-World | main | 1481a2de | SUCCESS | 10s
2025-06-24T10:16:00Z | api | main | 2592b3ef | FAILED | 5s | error: build failed
Log Format:
- Deployment logs:
timestamp | repository | branch | commit | status | duration | error - System logs:
timestamp | level | message - Web viewer adds project prefixes:
[project-name]or[SYSTEM]for easy identification
Access real-time logs through the web interface (default /admin/logs, configurable):
# View logs in your browser
http://localhost:3000/logs?limit=50Features:
- Project identification - Each log line shows which project it belongs to:
[my-app]for deployment logs from specific repositories[SYSTEM]for general server messages[UNKNOWN]for unrecognized log formats
- Dark theme optimized for log viewing
- Configurable limits (10, 20, 50, 100, 200 lines)
- Auto-refresh every 30 seconds
- Manual refresh button
- Textual level/status tags (e.g., INFO, ERROR where applicable)
- Mobile-friendly responsive design
- Secure - rate limited to prevent abuse (30 requests/minute per IP)
Example log output:
[SYSTEM] 2025-06-24T11:26:30-04:00 | INFO | Server initialized
[my-app] 2025-06-24T11:26:30-04:00 | my-app | main | abc123 | SUCCESS | 2.5s
[api-service] 2025-06-24T11:26:31-04:00 | api-service | develop | def456 | FAILED | error: build failed
Monitor the /health endpoint for service status and configuration.
-
Configuration file not found:
- Run the application once to create default config
- Check the search locations listed above
- Ensure file permissions are correct
-
Webhook not received:
- Check GitHub webhook configuration
- Verify webhook secret matches config.toml
- Check server logs for signature verification errors
-
Deployment fails:
- Check repository mapping in
[repositories]section - Verify local path exists and is accessible
- Check deployment commands are correct
- Review timeout settings
- Check repository mapping in
-
Permission errors:
- Ensure server has access to local repositories
- Check file permissions on deployment paths
- Verify user has necessary privileges for commands
Problem: If you use nvm to manage Node.js and your deployment commands fail with errors like:
Command failed: pnpm install - exit status 127
Command failed: npm run build - exit status 127
Why this happens: Systemd services run with a minimal PATH that doesn't include nvm-managed tools (node, npm, pnpm, yarn, pm2, etc.). Your interactive shell loads nvm automatically from ~/.bashrc or ~/.zshrc, but systemd doesn't run those files.
Solution Options:
Option 1: Source nvm in your deployment commands (Recommended)
Add source ~/.nvm/nvm.sh to the beginning of each command:
[commands]
"my-app" = "bash -c 'source ~/.nvm/nvm.sh && cd ~/my-app && git pull && pnpm install && pnpm build && pm2 restart my-app'"Why this is best:
- ✅ Works with any Node version you switch to via nvm
- ✅ Uses your nvm default or project-specific
.nvmrc - ✅ No need to update paths when upgrading Node
- ✅ Portable across different servers
Option 2: Specify Node version explicitly
Use nvm use to pin a specific version:
[commands]
"my-app" = "bash -c 'source ~/.nvm/nvm.sh && nvm use 22 && cd ~/my-app && git pull && pnpm install && pnpm build && pm2 restart my-app'"Why use this:
- ✅ Guarantees specific Node version per app
- ✅ Prevents breakage if you change nvm default
⚠️ Need to update command when upgrading Node major version
Option 3: Add nvm path to systemd service (Not Recommended)
Edit /etc/systemd/system/cicd-thing.service:
[Service]
Environment=PATH=/home/admin/.nvm/versions/node/v22.13.1/bin:/usr/local/bin:/usr/bin:/binWhy avoid this:
- ❌ Breaks when you upgrade Node (hardcoded version path)
- ❌ Affects ALL deployments (can't use different versions per app)
- ❌ Need systemctl daemon-reload and restart after Node upgrades
Testing:
Verify your command works outside systemd:
# Simulate systemd environment (no PATH, no shell initialization)
/usr/bin/env -i bash -c 'source ~/.nvm/nvm.sh && which pnpm && pnpm --version'If this works, your deployment command will work in systemd.
Enable dry run mode for testing:
dry_run = trueThis will simulate deployments without executing commands.
The application validates your configuration on startup and will show clear error messages for:
- Missing required fields
- Invalid TOML syntax
- Incorrect file paths
- Network configuration issues
This project is functional but could be more user-friendly. If you have ideas for improvements, better documentation, easier setup processes, or just want to make things clearer - your contributions are welcome!
Some areas that could use help:
- Improving the initial setup experience
- Better error messages and troubleshooting guides
- UI/UX improvements for the log viewer
- Additional documentation and examples
- Testing (currently no tests!)
- Feature requests and bug reports
Whether it's a small documentation fix or a major feature - all contributions help make this tool better for everyone.
How to contribute:
- Fork the repository
- Create a feature branch
- Make your improvements
- Submit a pull request
Not sure where to start? Open an issue to discuss ideas or ask questions. All skill levels welcome!
MIT License ncorrect file paths
- Network configuration issues
MIT License