Security Guide

Best practices for secure deployments with Pipe

Overview

Pipe is designed with security in mind:

  • No registry required — Images transfer directly via SSH, never touch public infrastructure
  • Input validation — All configuration values are validated against strict patterns
  • Shell injection prevention — Dangerous characters are blocked in user inputs

Handling Secrets

Environment Variables (Recommended for CI/CD)

Pass secrets through environment variables to avoid storing them in files or version control:

# Set secrets in your CI/CD pipeline
export DB_PASSWORD="your-secret"
export API_KEY="your-api-key"

# Use inline --env flags
pipe --env DB_PASSWORD="$DB_PASSWORD" --env API_KEY="$API_KEY"

In GitHub Actions:

- name: Deploy
  env:
    DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
    API_KEY: ${{ secrets.API_KEY }}
  run: |
    pipe --env DB_PASSWORD="$DB_PASSWORD" --env API_KEY="$API_KEY"

Inline Environment Variables in Config

You can also define environment variables directly in your pipe.yaml:

# pipe.yaml
env:
  NODE_ENV: production
  LOG_LEVEL: info
  DB_HOST: ${DB_HOST}  # Expanded from environment
⚠️ Warning: Never commit secrets to your config file. Use environment variable expansion (${VAR}) for sensitive values.

Environment Files

For multiple environment variables, use an env file:

# .env.production (DO NOT commit to git!)
DB_HOST=db.example.com
DB_PASSWORD=supersecret
API_KEY=sk-1234567890
REDIS_URL=redis://localhost:6379

Deploy with the env file:

pipe --env-file .env.production

Or in pipe.yaml:

envFile: .env.production

How Env File Transfer Works

  1. Pipe copies the env file to ~/<filename> on the remote host via SCP
  2. Docker runs the container with --env-file ~/<filename>
  3. The env file remains on the server for subsequent deployments

Combining Env File and Inline Variables

You can use both approaches together — inline variables override file values:

# pipe.yaml
envFile: .env.production
env:
  LOG_LEVEL: debug  # Override for this deployment
# CLI takes highest priority
pipe --env LOG_LEVEL=trace
💡 Tip: Add .env* to your .gitignore to prevent accidentally committing secrets.

SSH Security

SSH Key Authentication

Always use SSH keys instead of passwords:

# Generate a dedicated deployment key
ssh-keygen -t ed25519 -f ~/.ssh/deploy_key -C "deploy@myapp"

# Add to remote server
ssh-copy-id -i ~/.ssh/deploy_key.pub user@server.com

# Use in deployment
pipe --ssh-key ~/.ssh/deploy_key

In pipe.yaml:

sshKey: ~/.ssh/deploy_key
sshPort: "22"  # Or custom port like "2222"

Container Security

Run as Non-Root User

Never run containers as root in production:

containerUser: "1000:1000"  # UID:GID

Drop All Capabilities

Use the principle of least privilege:

capDrop:
  - ALL           # Drop all capabilities first
capAdd:
  - NET_BIND_SERVICE  # Add only what you need

Read-Only Root Filesystem

Prevent container modifications:

readOnly: true
tmpfs:
  - /tmp
  - /var/run

Use Init Process

Handle zombie processes and signals properly:

init: true

Resource Limits

Prevent resource exhaustion attacks:

cpus: "1"       # Limit to 1 CPU
memory: "512m"  # Limit to 512MB RAM

Complete Security Configuration

# pipe.yaml - Production security hardened
containerUser: "1000:1000"
readOnly: true
init: true

capDrop:
  - ALL
capAdd:
  - NET_BIND_SERVICE

tmpfs:
  - /tmp
  - /var/run

cpus: "1"
memory: "512m"

healthCmd: "curl -f http://localhost:3000/health || exit 1"
healthInterval: "30s"
healthRetries: 3

logDriver: json-file
logOpts:
  max-size: "10m"
  max-file: "3"

Input Validation

Pipe validates all inputs to prevent shell injection attacks:

Validated Patterns

FieldAllowed Pattern
host Valid hostname or IP address
user Unix username (lowercase, alphanumeric)
image Lowercase alphanumeric with . _ / -
tag Alphanumeric with . _ -
containerName Alphanumeric with _ . -
platform Whitelisted values only

Blocked Characters

The following characters are blocked in paths and values to prevent shell injection:

; & | $ ` \ \n \r " ' < > ( ) { }

Path Security

  • Path traversal (..) is blocked in file paths
  • envFile and dockerfile must be relative paths
  • Absolute paths are rejected for security-sensitive fields

Remote Commands

Remote commands are validated for dangerous patterns. The following are blocked:

  • rm -rf /
  • mkfs
  • dd if=
  • > /dev/

CI/CD Best Practices

GitHub Actions Example

name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup SSH Key
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/deploy_key
          chmod 600 ~/.ssh/deploy_key
          
      - name: Deploy
        env:
          HOST: ${{ vars.DEPLOY_HOST }}
          HOST_USER: ${{ vars.DEPLOY_USER }}
          DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
        run: |
          pipe --ssh-key ~/.ssh/deploy_key --env DB_PASSWORD="$DB_PASSWORD"

Never Commit Secrets

Add to .gitignore:

.env
.env.*
*.pem
*.key
deploy_key

Use Environment Variables for Sensitive Config

# pipe.yaml - Safe to commit
host: ${DEPLOY_HOST}
user: ${DEPLOY_USER}
sshKey: ${SSH_KEY_PATH}

Security Checklist

Before deploying to production:

  • SSH key authentication configured (no passwords)
  • Secrets passed via environment variables, not config files
  • .env files excluded from git
  • Container runs as non-root user
  • Capabilities dropped (at minimum --cap-drop ALL)
  • Read-only filesystem enabled (with tmpfs for writeable paths)
  • Resource limits set (CPU and memory)
  • Health checks configured
  • Log rotation configured
  • Docker network isolation configured
  • Dry-run tested before production deployment
# Always preview first
pipe --dry-run