iop

Configuration Reference

Complete reference for iop.yml configuration options

Configuration Reference

Complete reference for all configuration options available in iop.yml.

Basic Structure

name: my-project # Required: Project name used for Docker networks and container naming

# Global settings
ssh: # SSH connection settings
  username: iop # SSH username (default from config)
  port: 22 # SSH port (default: 22)
  key_file: ~/.ssh/id_rsa # Path to SSH private key (optional)

proxy: # Global proxy settings
  image: elitan/iop-proxy:latest # Custom proxy image (optional)

apps: # Applications (zero-downtime deployments)
  web: # App name
    # App configuration here

services: # Services (direct replacement deployments)
  database: # Service name  
    # Service configuration here

Apps Configuration

Apps are user-facing applications that get zero-downtime blue-green deployments. They are built locally and transferred via SSH.

Basic App Configuration

apps:
  web:
    server: server1.com # Target server (single server per app)
    
    build: # Build configuration (for local builds)
      context: . # Build context (default: .)
      dockerfile: Dockerfile # Dockerfile path (default: Dockerfile)
      args: # Build arguments from environment
        - NODE_ENV
        - API_URL
      target: production # Multi-stage build target (optional)
      platform: linux/amd64 # Target platform (optional)

    proxy: # Reverse proxy configuration
      app_port: 3000 # Port your app runs on inside container
      hosts: # Custom domains (optional)
        - example.com
        - www.example.com
      ssl: true # Enable HTTPS (default: true)
      ssl_redirect: true # Redirect HTTP to HTTPS (optional)
      forward_headers: false # Forward X-Forwarded-* headers (optional)
      response_timeout: 30s # Request timeout (default: 30s)

    environment: # Environment variables
      plain: # Plain text variables (KEY=VALUE format)
        - NODE_ENV=production
        - PORT=3000
      secret: # Variables from .iop/secrets (KEY format)
        - DATABASE_URL
        - API_KEY

    health_check: # Health check configuration
      path: /up # Health endpoint (default: /up)

    replicas: 2 # Number of replicas (default: 1)

Advanced App Options

apps:
  api:
    server: api.example.com
    image: my-app/api # Docker image name (optional, auto-generated)
    
    # Multiple replicas for load balancing
    replicas: 3

    # Port mapping (rarely needed since proxy handles routing)
    ports:
      - "8080:8080"

    # Volume mounts
    volumes:
      - ./logs:/app/logs
      - app_data:/data

    # Custom registry (for pre-built images instead of local build)
    registry:
      url: ghcr.io # Registry URL (optional)
      username: myuser # Registry username
      password_secret: REGISTRY_PASSWORD # Password from .iop/secrets

    # Override default command
    command: "npm start --production"

Services Configuration

Services are infrastructure components (databases, caches, etc.) that get direct replacement during deployment. They use pre-built Docker images.

Basic Service Configuration

services:
  postgres:
    image: postgres:17 # Required: Docker image with tag
    server: db.example.com # Target server
    
    ports: # Port mapping
      - "5432:5432"
    
    volumes: # Persistent volumes
      - ./pgdata:/var/lib/postgresql/data
      - db_logs:/var/log/postgresql
    
    environment: # Environment variables
      plain:
        - POSTGRES_USER=postgres
        - POSTGRES_DB=myapp
      secret:
        - POSTGRES_PASSWORD # From .iop/secrets

Advanced Service Options

services:
  redis:
    image: redis:7-alpine
    server: cache.example.com
    
    # Custom registry for private images
    registry:
      url: private-registry.com
      username: myuser
      password_secret: REGISTRY_TOKEN
    
    # Override default command
    command: "redis-server --appendonly yes"
    
    # Multiple port mappings
    ports:
      - "6379:6379"
      - "16379:16379" # Sentinel port
    
    # Named and bind volumes
    volumes:
      - redis_data:/data # Named volume
      - ./redis.conf:/usr/local/etc/redis/redis.conf:ro # Bind mount (read-only)

Global Configuration

SSH Configuration

ssh:
  username: iop # Default SSH username for all servers
  port: 22 # Default SSH port
  key_file: ~/.ssh/id_rsa # Path to SSH private key

You can override SSH settings per server by using different usernames in your app/service server specifications.

Proxy Configuration

proxy:
  image: elitan/iop-proxy:latest # Custom proxy Docker image

The proxy handles:

  • SSL certificate management (Let's Encrypt)
  • HTTP to HTTPS redirects
  • Reverse proxy routing
  • Load balancing between app replicas

Environment Variables

Plain Environment Variables

environment:
  plain:
    - NODE_ENV=production # Simple key=value
    - DEBUG=app:* # Can contain special characters
    - PORT=3000

Secret Environment Variables

Sensitive values are stored in .iop/secrets and referenced by key name:

In iop.yml:

environment:
  secret:
    - DATABASE_URL
    - JWT_SECRET
    - STRIPE_API_KEY

In .iop/secrets:

DATABASE_URL=postgres://user:password@localhost:5432/myapp
JWT_SECRET=your-super-secret-jwt-key
STRIPE_API_KEY=sk_live_...

Build Arguments

For apps with build configuration, you can pass environment variables as build arguments:

apps:
  web:
    build:
      context: .
      args:
        - NODE_ENV # Will pass NODE_ENV from environment section
        - API_URL
    environment:
      plain:
        - NODE_ENV=production
        - API_URL=https://api.example.com

Health Checks

Apps should implement health check endpoints for zero-downtime deployments:

apps:
  web:
    health_check:
      path: /up # Default endpoint

Your application must respond with HTTP 200 at this endpoint when healthy:

// Express.js example
app.get('/up', (req, res) => {
  // Check database connectivity, etc.
  res.status(200).send('OK');
});
// Go example
http.HandleFunc("/up", func(w http.ResponseWriter, r *http.Request) {
    // Check dependencies
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("OK"))
})

Volumes

Named Volumes

volumes:
  - postgres_data:/var/lib/postgresql/data
  - app_logs:/app/logs

Bind Mounts

volumes:
  - ./data:/app/data # Relative to iop.yml location
  - /host/path:/container/path # Absolute paths
  - ./config/nginx.conf:/etc/nginx/nginx.conf:ro # Read-only

Volume Best Practices

  • Use named volumes for database data
  • Use bind mounts for configuration files
  • Ensure host directories exist and have proper permissions
  • Use read-only (:ro) for configuration files

Multi-Server Deployment

Each app/service can target a single server. For multi-server deployments, create multiple entries:

apps:
  web-primary:
    server: server1.com
    # ... config
    
  web-secondary:
    server: server2.com
    # ... same config
    
  web-cdn:
    server: cdn.server.com
    # ... same config with different domains
    proxy:
      hosts:
        - cdn.example.com

Registry Configuration

Global Registry

docker:
  registry: ghcr.io
  username: myuser
  # Password should be in .iop/secrets as DOCKER_REGISTRY_PASSWORD

Per-App/Service Registry

apps:
  web:
    registry:
      url: private-registry.com
      username: appuser
      password_secret: APP_REGISTRY_TOKEN

Registry configuration is only needed for:

  • Services using private images
  • Apps using pre-built images (instead of local build)

Apps with build configuration don't need registries (images are built locally and transferred via SSH).

Complete Example

Here's a complete example showing all major features:

name: my-fullstack-app

ssh:
  username: iop
  key_file: ~/.ssh/deployment_key

apps:
  web:
    server: web.example.com
    replicas: 2
    build:
      context: .
      dockerfile: Dockerfile.web
      args:
        - NODE_ENV
        - API_URL
    proxy:
      hosts:
        - myapp.com
        - www.myapp.com
      app_port: 3000
      ssl: true
    environment:
      plain:
        - NODE_ENV=production
        - API_URL=https://api.myapp.com
      secret:
        - JWT_SECRET
        - DATABASE_URL
    health_check:
      path: /health
    volumes:
      - ./logs:/app/logs

  api:
    server: api.example.com
    build:
      context: ./api
      dockerfile: Dockerfile
    proxy:
      hosts:
        - api.myapp.com
      app_port: 8000
    environment:
      plain:
        - NODE_ENV=production
        - CORS_ORIGIN=https://myapp.com
      secret:
        - DATABASE_URL
        - JWT_SECRET
        - STRIPE_SECRET_KEY
    health_check:
      path: /api/health

services:
  postgres:
    image: postgres:17
    server: db.example.com
    ports:
      - "5432:5432"
    environment:
      plain:
        - POSTGRES_USER=postgres
        - POSTGRES_DB=myapp
      secret:
        - POSTGRES_PASSWORD
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    server: cache.example.com
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    command: "redis-server --appendonly yes"

Configuration Validation

iop validates your configuration and provides helpful error messages:

  • Missing required fields - Clear error messages about what's missing
  • Invalid formats - Validation errors with suggestions
  • Reserved names - Apps/services cannot be named: init, status, proxy

Use iop --verbose to see detailed configuration validation information.

Environment-Specific Configurations

Create multiple configuration files for different environments:

# Development
iop.development.yml

# Staging  
iop.staging.yml

# Production
iop.production.yml

Deploy with specific configurations:

iop -c iop.staging.yml    # Deploy to staging
iop -c iop.production.yml # Deploy to production

This allows different:

  • Server targets
  • Environment variables
  • Replica counts
  • SSL settings
  • Domain configurations