A simple, no-nonsense deployment tool for Go applications on Linux servers with systemd
launchd is a lightweight CI/CD helper that deploys Go binaries to Linux servers from your laptop. No Docker, no complex pipelines—just simple, effective deployments using SSH and systemd.
- 🔨 Build your Go app locally
- 📦 Copy the binary to your server over SSH
- ⚙️ Create/refresh a systemd service
- 🗄️ Run database migrations (optional)
- ✅ Verify health before finishing
Perfect for side projects, MVPs, and small-scale deployments where simplicity matters.
Before you start, make sure you have:
- ✓ Go installed (1.21+)
- ✓ OpenSSH client (
ssh,scp) - ✓ A Go project with a
mainpackage
- ✓ Linux with systemd (Ubuntu, Debian, CentOS, Fedora, etc.)
- ✓ SSH access with sudo privileges
- ✓ Network port open (e.g., 8080)
- ✓ Health endpoint (e.g.,
GET /health→200 OK) - ✓ Configurable port (via flag or environment variable)
git clone https://github.com/yourusername/launchd.git
cd launchdgo build -o launchd ./cmd/launchdsudo mv launchd /usr/local/bin/Verify installation:
launchd --versionEnsure your app builds locally:
go build -o ./bin/myapp ./path/to/your/cmdMake sure it:
- Accepts a port configuration (flag or env var)
- Exposes a
/healthendpoint that returns200 OK
launchd deploy \
--host your-server.com \
--user deploy \
--app ./bin/myapp \
--port 8080 \
--timeout 60s┌─────────────┐
│ 1. BUILD │ Compile Go binary locally
└──────┬──────┘
│
┌──────▼──────┐
│ 2. TRANSFER │ SCP binary to server
└──────┬──────┘
│
┌──────▼──────┐
│ 3. SERVICE │ Create/update systemd unit
└──────┬──────┘
│
┌──────▼──────┐
│ 4. MIGRATE │ Run DB migrations (optional)
└──────┬──────┘
│
┌──────▼──────┐
│ 5. VERIFY │ Health check polling
└─────────────┘
- Build: Uses your local Go toolchain to compile the binary
- Transfer: Copies via
scpto/usr/local/bin/<app> - Service: Generates systemd unit file and runs:
systemctl daemon-reloadsystemctl enable <app>systemctl restart <app>
- Migrate: Executes migration tool if configured
- Health Check: Polls
http://<host>:<port>/healthuntil success
launchd deploy --host <server> --user <username> --app <binary> --port <port>launchd deploy \
--host server.com \
--user deploy \
--app ./bin/myapp \
--port 8080 \
--migrate \
--migration-cmd "migrate -path /path/to/migrations -database postgres://..."launchd deploy \
--host server.com \
--user deploy \
--app ./bin/myapp \
--port 8080 \
--health-path /api/health# Test SSH access first
ssh deploy@your-server.comSolutions:
- Verify SSH keys are set up:
ssh-copy-id deploy@your-server.com - Check firewall rules allow SSH (port 22)
Problem: User cannot run systemctl without password
Solution: Add sudo permissions for systemctl:
# On the server, edit sudoers
sudo visudo
# Add this line (replace 'deploy' with your username)
deploy ALL=(ALL) NOPASSWD: /bin/systemctl, /usr/bin/install# Check what's using the port
sudo lsof -i :8080
# Stop the conflicting service
sudo systemctl stop conflicting-servicePossible causes:
- App takes too long to start (increase
--timeout) - Wrong port or health endpoint path
- App crashes on startup
Check logs:
ssh deploy@server.com 'sudo journalctl -u myapp.service -f'# Check if service is running
ssh deploy@server.com 'sudo systemctl status myapp.service'
# View recent logs
ssh deploy@server.com 'sudo journalctl -u myapp.service --since "10 minutes ago"'ssh user@server 'sudo systemctl status myapp.service'# Follow logs in real-time
ssh user@server 'sudo journalctl -u myapp.service -f'
# Last 100 lines
ssh user@server 'sudo journalctl -u myapp.service -n 100'ssh user@server 'sudo systemctl restart myapp.service'ssh user@server 'sudo systemctl stop myapp.service'ssh user@server << 'EOF'
sudo systemctl stop myapp.service
sudo systemctl disable myapp.service
sudo rm -f /usr/local/bin/myapp
sudo rm -f /etc/systemd/system/myapp.service
sudo systemctl daemon-reload
EOFGood news: launchd is idempotent! You can run the same deploy command multiple times safely.
# Make changes to your code
# Then deploy again
launchd deploy --host server.com --user deploy --app ./bin/myapp --port 8080What happens:
- ✅ Binary is overwritten with new version
- ✅ Service is restarted with zero-downtime goal
- ✅ Health check confirms new version is running
Do I need Docker?
No! launchd deploys native binaries directly. No containers required.
Do I need Go installed on the server?
No. Building happens locally on your machine. The server only runs the pre-compiled binary.
Do I need root access?
You need a user with sudo privileges to:
- Copy files to
/usr/local/bin - Manage systemd services
You don't need to log in as root directly.
Can I deploy to multiple servers?
Yes! Run the deploy command once per server, or write a simple bash loop:
for server in server1.com server2.com server3.com; do
launchd deploy --host $server --user deploy --app ./bin/myapp --port 8080
doneWhat about environment variables?
Add them to your systemd service file or use a tool like envdir. Future versions of launchd may support this natively.
Is this production-ready?
launchd works great for:
- ✅ Side projects
- ✅ MVPs and prototypes
- ✅ Internal tools
- ✅ Small-scale production apps
For large-scale production with complex requirements, consider Kubernetes, Nomad, or managed platforms.
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
Built with ❤️ for developers who prefer simplicity over complexity.
Why launchd? Because sometimes you just want to ship code, not configure orchestrators.
Documentation • Installation • Quick Start • Troubleshooting
Made with Go • Deployed with launchd