Skip to main content

Deploying Applications from Git

RunOS enables you to deploy your own applications directly from GitHub repositories with a Heroku-like experience. Push your code to Git, and RunOS handles the build, deployment, SSL certificates, and scaling automatically.

What You Can Deploy

Deploy any application that can run in a Docker container:

  • Web applications - Node.js, Python, Ruby, Go, PHP, Java
  • APIs and microservices - REST APIs, GraphQL servers, gRPC services
  • Background workers - Job queues, scheduled tasks, data processors
  • Static sites - React, Vue, Next.js, or any static site generator

Prerequisites

Before deploying your first application, ensure you have:

Required Services

  1. Harbor - Container registry for storing built Docker images

    • Deploy from Services → Marketplace → Harbor
    • Harbor uses MinIO as its storage backend
  2. MinIO - Object storage for Harbor's image storage

    • Deploy from Services → Marketplace → MinIO
    • Harbor automatically configures MinIO
  3. Traefik - Ingress controller (automatically included with RunOS)

    • Handles HTTP/HTTPS routing to your applications
    • Manages SSL certificate requests

First-Time Setup: When you navigate to Applications from the top navigation for the first time without these services installed, the RunOS console will automatically guide you through installing Harbor and MinIO with recommended configurations. Simply follow the prompts to set up the required infrastructure.

GitHub Repository

Your application repository must include a Dockerfile:

  • BuildKit: Requires Dockerfile in the repository root
  • GitHub Actions (ARC): Dockerfile location is flexible, defined in your workflow file

Deployment Methods

RunOS offers two build strategies, each with different trade-offs:

BuildKit (In-Cluster Builds)

How it works:

  • Builds run inside your Kubernetes cluster using BuildKit
  • Simple one-click setup with no external configuration
  • Supports multi-architecture builds (AMD64/ARM64)

Advantages:

  • ✅ Quick setup - just connect your GitHub account
  • ✅ No external infrastructure needed
  • ✅ Builds run on your own servers
  • ✅ Multi-architecture support out of the box

Requirements:

  • GitHub authentication to RunOS
  • Manual "Deploy Now" button click to trigger builds

Best for:

  • Getting started quickly
  • Simple deployment workflows
  • Teams that prefer manual deployment control

GitHub Actions Runner Controller (ARC)

How it works:

  • Self-hosted GitHub Actions runners run in your cluster
  • GitHub workflows trigger builds automatically
  • You own the GitHub App and control all credentials
  • RunOS acts on behalf of your GitHub App to pull code

Advantages:

  • ✅ Automatic deployments on git push
  • ✅ Full GitHub Actions workflow customization
  • ✅ You own and control the GitHub App
  • ✅ Supports complex CI/CD pipelines

Requirements:

  • GitHub organization (not personal accounts)
  • Create and manage an organization-level GitHub App
  • Provide RunOS with GitHub App credentials (App ID, Installation ID, Private Key)
  • Add a workflow file to your repository

Best for:

  • Production environments
  • Teams wanting automated deployments
  • Complex CI/CD workflows
  • Organizations with existing GitHub Actions experience

Setting Up Integrations

Before deploying applications, you need to add a GitHub integration. RunOS supports two integration types: BuildKit and GitHub Actions Runner Controller (ARC).

Adding an Integration

  1. Navigate to Applications in the main menu
  2. Click Add Integration in the top right
  3. Select your integration type:
    • BuildKit - For simple, manual deployments
    • GitHub Self-Hosted Runners (ARC) - For automated CI/CD pipelines

Once you've added an integration, you can create applications that use it.


Deploying with BuildKit

This is the fastest way to get started. BuildKit requires authentication to the main RunOS application, which allows RunOS to pull code from your private GitHub repositories.

Step 1: Add BuildKit Integration

  1. Navigate to Applications in the main menu
  2. Click Add Integration in the top right
  3. Select BuildKit
  4. Click Authorize GitHub to connect your account
  5. Grant RunOS access to your repositories
  6. Complete the integration setup

Step 2: Create Application

  1. Click Add Application (or the + button)
  2. Select your BuildKit integration from the dropdown
  3. Configure your application:

Application Details:

  • Name - Human-readable name for your application
  • Integration - Select your BuildKit integration
  • Repository - Select from your GitHub repositories
  • Branch - Choose the branch to deploy (e.g., main, production)

Build Configuration:

  • Platform - Select target architectures:
    • AMD64 (default, most common)
    • ARM64 (for ARM-based servers)
    • Both (multi-architecture images)

Resource Allocation:

  • CPU - Millicores (1000 mC = 1 CPU core)
    • Requests: Guaranteed CPU (default: 250 mC)
    • Limits: Maximum CPU (default: 500 mC)
  • Memory - Megabytes
    • Requests: Guaranteed memory (default: 500 MB)
    • Limits: Maximum memory (default: 1000 MB)

Replicas:

  • Number of pods to run (default: 1)
  • Increase for high availability and load balancing

Step 3: Configure Ports and Domains

Port Configuration:

Each application can expose multiple ports. For each port, specify:

  • Port number (1-65535) - The port your application listens on
  • Domains - One or more domains/paths to access this port

Access Methods:

For each port, your application is automatically accessible via:

  1. RunOS Cluster Domain (always created automatically):

    https://app-{appId}-{port}.{clusterDomain}

    Example: https://app-abc123-8080.cluster.runos.io

  2. Custom Domain with SSL:

    • Enter your domain (e.g., myapp.com)
    • Enter path (default: /)
    • Check Request SSL Certificate
    • Point your domain DNS to any worker node IP
    • Ensure port 80 is open for Let's Encrypt verification
    • Access via: https://myapp.com
  3. Custom Domain without SSL:

    • Enter your domain
    • Leave Request SSL Certificate unchecked
    • Access via: http://myapp.com:8891

Multiple ports example:

  • Port 8080: Web UI on app.example.com
  • Port 8081: API on api.example.com
  • Port 9090: Metrics on cluster domain only

Step 4: Environment Variables (Optional)

Add environment variables your application needs:

NODE_ENV=production
DATABASE_URL=postgresql://user:pass@host:5432/db
API_KEY=your-secret-key
# Lines starting with # are comments

Format:

  • One variable per line: KEY=VALUE
  • Everything after first = becomes the value
  • Comments start with #
  • Empty lines ignored

Security:

  • Environment variables are stored as Kubernetes secrets
  • Encrypted at rest
  • Never logged or exposed in the console

Step 5: Secure Files (Optional)

Upload sensitive files your application needs (certificates, credential files, etc.):

Supported files:

  • SSL certificates (.crt, .pem)
  • Private keys
  • Service account credentials (.json)
  • Configuration files
  • Maximum size: 32KB per file

Mount Options:

Read-Only (Non-Deletable):

  • File permanently mounted from Kubernetes secret
  • Cannot be modified or deleted by application
  • Best for certificates and static credentials
  • Example path: /app/certs/ssl.crt

Writable (Deletable):

  • File copied to writable volume on startup
  • Application can modify or delete
  • Useful for files loaded into memory then deleted for security
  • Example: Load credential, delete file after reading

Upload Process:

  1. Click Add Secure File
  2. Drag and drop or select file
  3. Specify mount path (e.g., /app/config/credentials.json)
  4. Choose deletable or non-deletable
  5. SHA256 hash calculated automatically for verification

Step 6: Deploy

  1. Review all configuration

  2. Click Create Application

  3. RunOS will:

    • Create dedicated namespace: app-{appId}
    • Set up Harbor credentials
    • Prepare routing and ingress
    • Save configuration
  4. Click Deploy Now to trigger first build

  5. Monitor build progress in the application detail page

Build Process:

  • BuildKit pulls your code from GitHub
  • Builds Docker image using your Dockerfile
  • Pushes image to Harbor: {harbor}/runos-apps/{appId}:{commitHash}
  • Deploys to Kubernetes
  • Creates ingress rules for domains

First deployment: 3-10 minutes depending on image size Subsequent deployments: 1-5 minutes

Step 7: Redeploy When Needed

With BuildKit, deployments are manual:

  1. Push code changes to GitHub
  2. Navigate to Applications → Manage → [Your App]
  3. Click Deploy Now
  4. RunOS pulls latest code and rebuilds

Why manual? We're gathering user feedback on automatic deployment triggers before implementing them. Most teams prefer manual control for production deployments.


Deploying with GitHub Actions Runner Controller (ARC)

For automatic deployments and full CI/CD control, use self-hosted GitHub Actions runners. This requires more setup but provides complete automation.

Step 1: Add ARC Integration

ARC requires an organization-level GitHub App that you own and control. RunOS will act on behalf of your app to pull code.

  1. Navigate to Applications in the main menu
  2. Click Add Integration in the top right
  3. Select GitHub Self-Hosted Runners (ARC)
  4. Follow the guided setup wizard in the RunOS console

The console will guide you through:

  • Creating a GitHub App in your organization
  • Setting the required permissions
  • Generating and providing a private key
  • Installing the app on your organization
  • Providing the Installation ID

What you need:

  • A GitHub organization (not a personal account)
  • Admin access to the organization
  • Ability to create GitHub Apps

Security Note: You own and control the GitHub App. RunOS stores the private key as a base64-encoded Kubernetes secret in your cluster - it never leaves your infrastructure.

Step 2: Create Application

  1. Click Add Application (or the + button)
  2. Select your ARC integration from the dropdown
  3. Configure your application:

Application Details:

  • Name - Human-readable name for your application
  • Integration - Select your ARC integration
  • Repository - Select from your GitHub repositories
  • Branch - Choose the branch to deploy (e.g., main, production)

Configuration: Similar to BuildKit, configure:

  • Resource allocation (CPU, memory, replicas)
  • Environment variables
  • Ports and domains
  • Secure files

Key Difference: No manual "Deploy Now" button. Deployments trigger via GitHub workflows.

Step 3: Add Workflow to Repository

RunOS generates a sample workflow file for you. After creating your application:

  1. Copy the generated workflow from the success page
  2. In your repository, create: .github/workflows/runos-ci-{accountId}-{cid}.yaml
  3. Paste the workflow content
  4. Commit and push to your repository

Sample Workflow Structure:

name: RunOS CI/CD
on:
push:
branches: [main] # Your deployment branch
workflow_dispatch: # Manual trigger option

jobs:
build-and-deploy:
runs-on: runos-default-arc-runners-{accountId}-{cid}
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup kubectl and credentials
run: |
# Sets up Harbor credentials from Kubernetes

- name: Build and push to Harbor
run: |
# Builds image and pushes to Harbor registry

- name: Deploy to Kubernetes
run: |
# Updates Kubernetes deployment with new image

Step 4: Automatic Deployments

With ARC integration and workflow configured:

  1. Push to your deployment branch (e.g., main)
  2. GitHub automatically triggers workflow
  3. Workflow runs on your self-hosted runner in the cluster
  4. Application builds, pushes to Harbor, and deploys
  5. Monitor progress in GitHub Actions tab

Deployment time: 2-8 minutes depending on build complexity

Customization:

  • Modify workflow to add tests, linting, security scans
  • Add deployment approvals or gates
  • Customize build steps for your stack
  • Deploy on tags, pull requests, or schedules

SSL Certificates and Let's Encrypt

RunOS uses Let's Encrypt to provide free SSL certificates for your custom domains.

Requirements for SSL

To request an SSL certificate for a custom domain:

  1. Domain DNS must point to a worker node IP address

    • Use an A record: myapp.com → 203.0.113.50
    • Or CNAME to worker node hostname
  2. Port 80 must be open on the worker node

    • Let's Encrypt validates ownership via HTTP-01 challenge
    • Traefik handles the challenge automatically
    • Port can be closed after initial certificate issuance
  3. Check "Request SSL Certificate" when adding the domain

Certificate Process

  1. You add domain and check Request SSL Certificate
  2. You deploy application
  3. Traefik (via cert-manager) detects new domain
  4. Let's Encrypt sends HTTP challenge to http://yourdomain.com/.well-known/acme-challenge/{token}
  5. Traefik responds automatically
  6. Let's Encrypt issues certificate
  7. Certificate automatically installed
  8. Your app becomes accessible via https://yourdomain.com

Certificate renewal: Automatic every 60 days (Let's Encrypt certs expire after 90 days)

Troubleshooting SSL

Certificate not issuing:

  • Verify DNS points to worker node: dig yourdomain.com
  • Verify port 80 is open: curl http://yourdomain.com/.well-known/acme-challenge/test
  • Check cert-manager logs: kubectl logs -n cert-manager -l app=cert-manager

Certificate pending forever:

  • Let's Encrypt has rate limits (50 certs per domain per week)
  • DNS may not have propagated (wait 24-48 hours)
  • Firewall may be blocking port 80

Using External Load Balancers

If you're using an external load balancer (AWS ALB, Azure Load Balancer, HAProxy, etc.) in front of your RunOS cluster, you have two SSL termination options:

Option 1: SSL Termination on Load Balancer

Terminate SSL at the load balancer and forward unencrypted traffic to RunOS:

Configuration:

  1. Configure SSL certificate on your load balancer
  2. Forward traffic to RunOS nodes on port 8891 (plain HTTP)
  3. Do NOT check "Request SSL Certificate" when adding the domain in RunOS
  4. Ensure secure network between load balancer and RunOS nodes (VPC, VPN, or private network)

Traffic Flow:

User → HTTPS (443) → Load Balancer → HTTP (8891) → RunOS Node → Application

Benefits:

  • ✅ Centralized certificate management on load balancer
  • ✅ No port 80 requirement on RunOS nodes
  • ✅ SSL offloading reduces RunOS node workload

Security Note: The network between your load balancer and RunOS nodes must be secure. Use:

  • Private VPC/subnet
  • VPN tunnels
  • Firewall rules restricting 8891 to load balancer IPs only

Option 2: SSL Termination on RunOS (End-to-End Encryption)

Let RunOS handle SSL certificates via Let's Encrypt:

Configuration:

  1. Point domain DNS to RunOS worker node (not load balancer)
  2. Configure load balancer to pass through traffic (TCP mode, port 443)
  3. CHECK "Request SSL Certificate" when adding the domain in RunOS
  4. Ensure port 80 is accessible for Let's Encrypt validation

Traffic Flow:

User → HTTPS (443) → Load Balancer (passthrough) → HTTPS (443) → RunOS Node → Application

Benefits:

  • ✅ End-to-end encryption
  • ✅ Free automatic certificates via Let's Encrypt
  • ✅ No certificate management on load balancer

Requirements:

  • Load balancer must support TCP passthrough mode
  • Port 80 must be accessible for Let's Encrypt HTTP-01 challenge
  • DNS points directly to RunOS nodes

Understanding Application URLs

Every deployed application gets multiple URLs automatically:

1. RunOS Cluster Domain (Always Created)

For each exposed port:

https://app-{appId}-{port}.{clusterDomain}

Characteristics:

  • ✅ Always works immediately
  • ✅ Automatic SSL certificate
  • ✅ No DNS configuration needed
  • ✅ Accessible from anywhere

Example:

  • App ID: abc123
  • Port: 8080
  • Cluster domain: cluster.runos.io
  • URL: https://app-abc123-8080.cluster.runos.io

2. Custom Domain with SSL

When you add a custom domain with SSL requested:

https://yourdomain.com{path}

Characteristics:

  • ✅ Your own domain
  • ✅ Free Let's Encrypt SSL
  • ✅ Automatic renewal
  • ⚠️ Requires DNS configuration
  • ⚠️ Requires port 80 open

3. Custom Domain without SSL

When you add a custom domain without SSL:

http://yourdomain.com:8891{path}

Characteristics:

  • ✅ Your own domain
  • ✅ No port 80 requirement
  • ⚠️ No encryption (use behind load balancer or internal services only)
  • ⚠️ Requires port 8891 open

Note: This option is commonly used when SSL is terminated on an external load balancer (see Using External Load Balancers section).

4. Internal Service URL

For service-to-service communication within cluster:

http://app-{appId}.app-{appId}.svc.cluster.local:{port}

Characteristics:

  • ✅ Fast (no external routing)
  • ✅ Always available
  • ⚠️ Only accessible from other pods in cluster

Managing Deployed Applications

Application Dashboard

Navigate to Applications → Manage → [Your App] to see:

Overview Tab:

  • Application status (Healthy/Degraded/Unhealthy)
  • Current image and commit hash
  • Replica count and resource usage
  • Access URLs
  • Recent deployments

Pods Tab:

  • Live view of all running pods
  • Pod status, restarts, age
  • Resource usage per pod
  • Quick restart action

Logs Tab:

  • Real-time logs from all pods
  • Search and filter
  • Download logs
  • Tail control (last N lines)

Metrics Tab:

  • CPU usage over time
  • Memory usage over time
  • Network traffic
  • Request rates (if exposed)

Common Actions

Update Configuration:

  1. Click Edit in application dashboard
  2. Modify environment variables, resources, or domains
  3. Click Save
  4. Changes apply on next deployment

Redeploy:

  • BuildKit: Click Deploy Now
  • ARC: Push to Git or trigger workflow manually

Restart Pods:

  1. Go to Pods tab
  2. Click Restart on specific pod, OR
  3. Click Restart All in Danger Zone

Scale Replicas:

  1. Click Edit
  2. Change replica count
  3. Save and redeploy

Delete Application:

  1. Scroll to Danger Zone
  2. Click Delete Application
  3. Confirm deletion
  4. Namespace app-{appId} and all resources deleted

Best Practices

Security

Secrets Management:

  • ✅ Use environment variables for secrets, not hardcoded
  • ✅ Upload sensitive files via Secure Files feature
  • ✅ Use deletable files for one-time credentials
  • ❌ Never commit secrets to Git
  • ❌ Never log secrets in application code

Network Security:

  • ✅ Use HTTPS for all public-facing applications
  • ✅ Use internal service URLs for service-to-service communication
  • ✅ Limit exposed ports to only what's necessary
  • ❌ Don't expose debug ports publicly

Resource Allocation

Right-sizing:

  • Start small (250 mC CPU, 500 MB memory)
  • Monitor actual usage in Metrics tab
  • Increase if hitting limits (pod restarts, slow performance)
  • Set limits slightly higher than requests

CPU:

  • 100-250 mC: Simple APIs, static sites
  • 250-500 mC: Web apps, small databases
  • 500-1000 mC: Heavy processing, batch jobs
  • 1000+ mC: CPU-intensive workloads

Memory:

  • 128-256 MB: Minimal applications
  • 256-512 MB: Simple web apps
  • 512-1024 MB: Standard applications
  • 1024-2048 MB: In-memory caching, large apps
  • 2048+ MB: Heavy memory usage

High Availability

Multiple Replicas:

  • Set replicas to 2+ for production applications
  • Kubernetes distributes across nodes automatically
  • If one pod fails, others continue serving traffic
  • Load balancing handled by Traefik

Resource Limits:

  • Set both requests and limits
  • Prevents one app from consuming all node resources
  • Kubernetes reschedules pods if node fails

Health Checks (Advanced):

  • Implement /health or /ready endpoints
  • Configure liveness and readiness probes in Kubernetes
  • Automatic pod replacement on health check failures

Deployment Strategy

Development Workflow:

  • Use BuildKit for simplicity
  • Deploy to development cluster frequently
  • Test thoroughly before promoting to production

Production Workflow:

  • Use ARC for automatic deployments
  • Deploy to staging environment first
  • Use Git tags or release branches
  • Monitor logs and metrics after deployment

Rollback:

  • If deployment fails, previous version keeps running
  • Revert Git commit and push
  • Or manually specify previous image in deployment

Comparison: BuildKit vs ARC

FeatureBuildKitGitHub Actions (ARC)
Setup ComplexitySimple (5 minutes)Complex (20 minutes)
GitHub RequirementGitHub accountGitHub organization
Deployment TriggerManual "Deploy Now"Automatic on push
CustomizationLimitedFull workflow control
Build LocationIn-clusterIn-cluster
Multi-arch Support✅ Yes✅ Yes (via workflow)
Workflow FilesNot neededRequired in repo
CI/CD PipelineNot supportedFully supported
Best ForDevelopment, simple appsProduction, automated pipelines
GitHub App OwnershipRunOS managesYou own and control
CredentialsRunOS GitHub authYour GitHub App credentials

Troubleshooting

Build Failures

"Failed to pull repository"

  • Verify GitHub integration is connected
  • Check repository access permissions
  • Re-authorize GitHub if token expired

"Dockerfile not found"

  • Ensure Dockerfile exists in repository root
  • Check Dockerfile capitalization (case-sensitive)
  • Verify selected branch has Dockerfile

"Build timeout"

  • Increase build resources in cluster
  • Optimize Dockerfile (use smaller base images)
  • Use Docker layer caching

Deployment Issues

"ImagePullBackOff"

  • Harbor credentials may be incorrect
  • Check Harbor is running: kubectl get pods -n {harborOsid}
  • Verify image was pushed successfully

Pods in "CrashLoopBackOff"

  • Application is starting but crashing
  • Check logs: Applications → Manage → Logs
  • Verify environment variables are correct
  • Check resource limits aren't too low

"No healthy pods"

  • Check all pods status in Pods tab
  • Review events in pod details
  • Verify port configuration matches application

Domain and SSL Issues

Domain not accessible

  • Verify DNS: dig yourdomain.com
  • Check domain is added in port configuration
  • Ensure Traefik is running: kubectl get pods -n traefik

SSL certificate not issued

  • Verify port 80 is open on worker node
  • Check cert-manager is running: kubectl get pods -n cert-manager
  • View certificate status: kubectl get certificate -n app-{appId}
  • Let's Encrypt rate limits may apply

"Your connection is not private" SSL error

  • Certificate may still be issuing (wait 2-5 minutes)
  • Check certificate status in application dashboard
  • Verify domain DNS is correct

Next Steps

Now that you know how to deploy applications:

  1. Deploy a sample app - Try with a simple Node.js or Python app first
  2. Add monitoring - Deploy Grafana from marketplace to visualize metrics
  3. Set up alerts - Configure alerts based on application health
  4. Explore services - Connect your app to PostgreSQL, Redis, or other marketplace services
  5. Scale up - Add more nodes for high availability

Related Guides: