January 28, 2026
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.
| 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.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 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.
What you need:
Time estimate: 30 minutes to set up, 5 minutes per deployment after
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
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
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
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:
| 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.
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
β
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
# 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:
You should see:
If deployment fails, the error messages are verboseβscroll through the logs to debug.
| 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.
~/.ssh/authorized_keys on Oracle serverssh -i ~/.ssh/rust_deploy ubuntu@YOUR_IPcargo build on Oracle server to set up environmentsudo journalctl -u newsletter -n 50ExecStart in systemd service filels -la target/release/newsletter--incremental or --codegen-units for faster buildsOnce you're comfortable, add these improvements:
- name: Run migrations
run: |
sqlx migrate run --database-url postgres://... || true
- name: Notify deployment
run: |
curl -X POST ${{ secrets.SLACK_WEBHOOK }} \
-d 'Deployed to production: ${{ github.sha }}'
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
# In deploy script
if [ -f /home/ubuntu/newsletter/.env.prod ]; then
source /home/ubuntu/newsletter/.env.prod
fi
| 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.
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
sudo journalctl -u newsletter -f during first deploymentYou don't need expensive CI/CD platforms to ship production code reliably. GitHub Actions + SSH + systemd gives you:
Start small, deploy confidently, and save money doing it.
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! π