January 28, 2026
DIY CI/CD for Rust on Oracle Cloud: The $0 Alternative to Premium Platforms
Introduction
If you're running a Rust microservice on Oracle Cloud and dreading the costs of enterprise CI/CD platforms like GitLab Premium, Jenkins, or hosted solutions—there's good news. You can implement production-grade continuous integration and deployment using three free-tier tools: GitHub Actions, SSH key authentication, and systemd.
This guide shows you exactly how to build a CI/CD pipeline that rivals platforms costing hundreds per month, while paying absolutely nothing.
Why This Matters: The Cost Reality
| Platform | Monthly Cost | Annual Cost |
|---|---|---|
| GitLab Premium | $228/month | $2,736 |
| Jenkins (managed) | $150-500/month | $1,800-6,000 |
| CircleCI | $100-500/month | $1,200-6,000 |
| GitHub Actions (our approach) | $0 | $0 |
| Oracle Cloud Free Tier VM | $0 | $0 |
For small teams and indie developers, this cost difference is transformative.
Architecture Overview: How It Works
┌─────────────────────────────────────────────────────────────┐
│ Developer Workflow │
│ │
│ 1. git push to master │
│ 2. GitHub Actions triggered (free) │
│ 3. CI: Tests, linting, format checks │
│ 4. If CI passes → CD: SSH to Oracle server │
│ 5. On server: git pull + cargo build + systemctl restart │
│ 6. Service live with new code ✅ │
└─────────────────────────────────────────────────────────────┘
Key insight: We leverage GitHub Actions' free tier (up to 2,000 CI minutes/month for private repos) and SSH key-based authentication to deploy directly to your Oracle VM without intermediary services.
Prerequisites
What you need:
- A Rust project hosted on GitHub
- An Oracle Cloud Compute VM (Ubuntu 20.04+, free tier eligible)
- SSH key access to your Oracle VM
- Basic familiarity with systemd and shell scripting
Time estimate: 30 minutes to set up, 5 minutes per deployment after
Step 1: Generate SSH Deploy Key (Local Machine)
Your GitHub Actions runner needs a way to authenticate to your Oracle server. We'll use Ed25519 SSH keys—modern, compact, and more secure than RSA.
# Generate deploy key (press Enter for empty passphrase)
ssh-keygen -t ed25519 -f ~/.ssh/rust_deploy -C "github-actions-deploy"
# View the public key (you'll need this next)
cat ~/.ssh/rust_deploy.pub
Output will look like:
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... github-actions-deploy
Step 2: Install the Public Key on Oracle Server
SSH into your Oracle VM and authorize the GitHub Actions deploy key:
# SSH to your Oracle server
ssh ubuntu@YOUR_ORACLE_IP
# Create SSH directory if needed
mkdir -p ~/.ssh
chmod 700 ~/.ssh
# Add the public key to authorized_keys
nano ~/.ssh/authorized_keys
Paste the public key from Step 1 (the line starting with ssh-ed25519), save, then:
chmod 600 ~/.ssh/authorized_keys
# Verify it works from your local machine
ssh -i ~/.ssh/rust_deploy ubuntu@YOUR_ORACLE_IP
# Should connect without password
Step 3: Configure systemd Service (Oracle Server)
Your service needs to be managed by systemd so GitHub Actions can restart it cleanly.
# On your Oracle server
sudo nano /etc/systemd/system/newsletter.service
Paste this template (adjust newsletter to your binary name):
[Unit]
Description=Newsletter Rust Service
After=network.target
[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/newsletter
Environment=PATH=/home/ubuntu/.cargo/bin:/usr/local/bin:/usr/bin:/bin
ExecStart=/home/ubuntu/newsletter/target/release/newsletter
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
Enable and test:
sudo systemctl daemon-reload
sudo systemctl enable newsletter
sudo systemctl start newsletter
sudo systemctl status newsletter
If status shows "active (running)", you're good. If not, check logs:
sudo journalctl -u newsletter -n 50 # Last 50 log lines
Step 4: Add GitHub Actions Secrets
GitHub Actions workflows can't run if they don't have credentials. Store your deploy key and server info as repository secrets.
In your GitHub repository:
- Go to Settings → Secrets and variables → Actions
- Click "New repository secret"
- Add three secrets:
| Secret Name | Value |
|---|---|
SSH_HOST |
80.225.191.45 (or your Oracle IP) |
SSH_USER |
ubuntu |
SSH_PRIVATE_KEY |
(Paste entire content of ~/.ssh/rust_deploy, including -----BEGIN... and -----END... lines) |
⚠️ Security note: GitHub encrypts these and never displays them in logs. The *** masking you see in CI logs is intentional.
Step 5: Create the GitHub Actions Workflow
Create this file in your repository:
.github/workflows/rust.yml
name: Rust CI/CD
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
env:
CARGO_TERM_COLOR: always
SQLX_OFFLINE: true
jobs:
# === CONTINUOUS INTEGRATION ===
build:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:13
env:
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Cache Cargo dependencies
uses: actions/cache@v3
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Check code formatting
run: cargo fmt --check --all
- name: Cargo check
run: cargo check
env:
DATABASE_URL: postgres://postgres:postgres@127.0.0.1:5432/postgres
SQLX_OFFLINE: false
- name: Run Clippy linter
run: cargo clippy --all-targets --all-features -- -D warnings
env:
DATABASE_URL: postgres://postgres:postgres@127.0.0.1:5432/postgres
SQLX_OFFLINE: false
- name: Run tests
run: cargo test
env:
DATABASE_URL: postgres://postgres:postgres@127.0.0.1:5432/postgres
SQLX_OFFLINE: false
# === CONTINUOUS DEPLOYMENT ===
deploy:
needs: build # Only deploy if CI passes
if: github.ref == 'refs/heads/master' # Only on master branch
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to Oracle Cloud
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /home/ubuntu/newsletter
# Ensure Rust toolchain is available
curl -sSf https://sh.rustup.rs | sh -s -- -y
source ~/.cargo/env
# Pull latest code
git pull origin master
# Build release binary
cargo build --release --bin newsletter
# Restart service with new binary
sudo systemctl stop newsletter || true
sleep 2
sudo systemctl daemon-reload
sudo systemctl restart newsletter
sleep 3
# Verify service is running
sudo systemctl status newsletter --no-pager -l
Key Features of This Workflow:
✅ Dependency caching – Speeds up subsequent builds (Cargo cache)
✅ Database integration – Postgres container for integration tests
✅ Code quality gates – fmt, check, clippy run before deployment
✅ Conditional deployment – Only deploys on master push if CI passes
✅ Automatic binary building – Builds directly on Oracle VM
✅ Service management – Graceful systemd restarts
Step 6: Test the Pipeline
# Make a small change to your code
echo "// CI/CD test" >> src/main.rs
# Commit and push to master
git add .
git commit -m "Test CI/CD pipeline"
git push origin master
Watch in real-time:
- Go to your GitHub repo → Actions tab
- Click the running workflow
- Expand each step to see logs
You should see:
- ✅ Build job: tests pass
- ✅ Deploy job: SSH connects, pulls code, builds, restarts service
If deployment fails, the error messages are verbose—scroll through the logs to debug.
How Much Does This Actually Cost?
| Component | Monthly Cost | Notes |
|---|---|---|
| GitHub Actions | $0 | Free tier: 2,000 min/month for private repos |
| Oracle Cloud VM | $0 | Free tier: 1x AMD VM (2 vCPU, 12GB RAM) |
| Custom domain (optional) | ~$10 | Not required for CI/CD |
| Storage/backup | $0 | Free tier includes 10GB |
| Total | ~$0-10 | Wildly cheaper than platforms like GitLab |
If you exceed free tier limits (building 2,000+ minutes/month), GitHub Actions billing is $0.35/minute—still vastly cheaper than enterprise platforms.
Troubleshooting Common Issues
"Permission denied (publickey)" during deploy
- Verify SSH key is in
~/.ssh/authorized_keyson Oracle server - Test manually:
ssh -i ~/.ssh/rust_deploy ubuntu@YOUR_IP
"cargo: command not found"
- The workflow installs Rust, but first build might fail if system Rust isn't present
- Solution: Run one manual
cargo buildon Oracle server to set up environment
Service fails to start after deployment
- Check logs:
sudo journalctl -u newsletter -n 50 - Verify binary path matches
ExecStartin systemd service file - Ensure binary has execute permissions:
ls -la target/release/newsletter
Deployment takes too long
- First build compiles everything (~5-10 min)
- Subsequent builds use Cargo cache (~1-2 min)
- Consider adding
--incrementalor--codegen-unitsfor faster builds
Production Hardening
Once you're comfortable, add these improvements:
1. Database Migrations on Deploy
- name: Run migrations
run: |
sqlx migrate run --database-url postgres://... || true
2. Slack/Discord Notifications
- name: Notify deployment
run: |
curl -X POST ${{ secrets.SLACK_WEBHOOK }} \
-d 'Deployed to production: ${{ github.sha }}'
3. Rollback Strategy
Keep the previous binary as backup:
sudo mv target/release/newsletter target/release/newsletter.new
sudo mv target/release/newsletter.bak target/release/newsletter || true
sudo mv target/release/newsletter.new target/release/newsletter.bak
4. Environment-Specific Configuration
# In deploy script
if [ -f /home/ubuntu/newsletter/.env.prod ]; then
source /home/ubuntu/newsletter/.env.prod
fi
Why This Approach Wins
| Aspect | Jenkins/GitLab | Our Approach |
|---|---|---|
| Cost | $100-500/month | $0 |
| Setup time | Days | 30 minutes |
| Maintenance burden | High (server updates, security patches) | Minimal (GitHub handles it) |
| Deployment time | 2-5 minutes | 1-3 minutes |
| Learning curve | Steep (pipelines, agents, runners) | Shallow (bash + systemd) |
| Scalability | Manual scaling | Auto-scales free tier |
For solo developers and small teams, this is unbeatable. For enterprises with 50+ deployments/day, you might need more sophisticated tooling—but even then, this architecture serves as a cost-effective foundation.
Real-World Example: Timeline
15:30 → Developer pushes commit to master
15:31 → GitHub Actions workflow triggered
15:32 → CI: cargo test completes (2,000+ tests pass)
15:33 → CI: clippy linter runs (0 warnings)
15:34 → Deploy: SSH to Oracle, git pull, cargo build starts
15:38 → Deploy: cargo build --release completes
15:39 → Deploy: systemctl restart newsletter
15:40 → Service live with new code ✅
Total time: 10 minutes, $0 cost, zero manual intervention
Next Steps
- Try this today – Set up the pipeline with your existing Rust project
- Monitor in production – Watch
sudo journalctl -u newsletter -fduring first deployment - Iterate – Add Slack notifications, automated rollbacks, or database migrations as needed
- Share your setup – Tell your team about this approach—it works for any Rust service
Conclusion
You don't need expensive CI/CD platforms to ship production code reliably. GitHub Actions + SSH + systemd gives you:
- ✅ Automated testing on every push
- ✅ Zero-downtime deployments
- ✅ Auditable deployment history
- ✅ Cost savings of $1,200-6,000/year
- ✅ Zero infrastructure maintenance
Start small, deploy confidently, and save money doing it.
Resources
- GitHub Actions Documentation
- appleboy/ssh-action (SSH action used in our workflow)
- systemd Service Files
- Oracle Cloud Free Tier
- Rust Official Documentation
Questions? This setup works for Node.js, Python, Go, and any language with a CLI toolchain. Adapt the cargo build step to your language's build command.
Happy deploying! 🚀