Skip to content

FastDeploy Deployment Guide

This document explains how FastDeploy itself is deployed and how services are registered with it.

FastDeploy Self-Deployment

FastDeploy is deployed using the ops-control infrastructure automation system with the fastdeploy_deploy role from ops-library.

Deployment Methods

FastDeploy supports two deployment methods:

Development Deployment (rsync)

Uses local working directory for rapid development:

# Deploy from local development directory
just deploy-one fastdeploy

This method: - Syncs code from /Users/jochen/projects/fastdeploy via rsync - Ideal for testing changes before committing - Preserves file permissions and symlinks - Allows rapid iteration without git commits

Production Deployment (git)

Uses git repository for production deployment:

# Register FastDeploy for self-deployment
just register-one fastdeploy-self

# Then deploy via FastDeploy web UI

This method: - Clones from GitHub repository - Ensures reproducible deployments - Supports tagged releases - Used for production environments

Deployment Process

The fastdeploy_deploy role handles:

  1. Database Setup
  2. PostgreSQL database creation
  3. Database migrations
  4. Initial user creation

  5. Python Environment

  6. Virtual environment creation with uv
  7. Dependency installation from uv.lock

  8. Frontend Build

  9. Node.js/npm dependency installation
  10. Production build with Vite
  11. Static file serving configuration

  12. Service Configuration

  13. Systemd service setup
  14. Environment file creation
  15. Log rotation configuration

  16. Initialization

  17. Admin user creation
  18. Service synchronization from filesystem

Configuration

Key configuration variables:

# Database
fastdeploy_postgres_host: localhost
fastdeploy_postgres_database: fastdeploy
fastdeploy_postgres_user: fastdeploy

# FastDeploy settings
fastdeploy_site_path: /home/fastdeploy/site
fastdeploy_user: fastdeploy
fastdeploy_service_name: fastdeploy

# Deployment method
fastdeploy_deploy_method: rsync  # or git
fastdeploy_source_path: /Users/jochen/projects/fastdeploy  # for rsync
fastdeploy_git_repo: https://github.com/ephes/fastdeploy.git  # for git

FastDeploy also supports DEPLOYMENT_ORPHAN_RECONCILE_DELAY_SECONDS (default: 300) in its .env. This prevents brand-new deployments from being auto-finished before their steps are created.

Service Registration

Services are registered with FastDeploy using the fastdeploy_register_service role.

Registration Process

  1. Create Registration Playbook

Create playbooks/register-<service>.yml in ops-control:

---
- name: Register service with FastDeploy
  hosts: macmini
  become: true
  tasks:
    - name: Register service
      include_role:
        name: local.ops_library.fastdeploy_register_service
      vars:
        fd_service_name: "myservice"
        fd_service_description: "My service description"
        fd_fastdeploy_root: "/home/fastdeploy/site"
        fd_runner_content: |
          #!/usr/bin/env python3
          # Custom deployment script here
  1. Register the Service
just register-one myservice

Registration Components

The registration process creates:

Service Directory Structure

/home/fastdeploy/site/services/myservice/
├── config.json          # Service metadata for FastDeploy UI
└── deploy.py            # Deployment script (FastDeploy calls this)

/home/deploy/runners/myservice/
└── deploy.py            # Source deployment script

Security Configuration

  • Deploy user creation with restricted permissions
  • Sudoers rules for cross-user execution (fastdeploydeploy)
  • SOPS age keys for secrets decryption
  • Proper file permissions and ownership

Service Configuration

The config.json contains:

{
  "name": "myservice",
  "description": "Service description",
  "deploy_script": "deploy.py",
  "steps": [
    {"name": "prepare"},
    {"name": "bootstrap"},
    {"name": "ansible"},
    {"name": "verify"}
  ]
}

Deployment Script Requirements

Registered services must provide a deployment script that:

  1. Accepts Configuration python parser = argparse.ArgumentParser() parser.add_argument('--config', help='Configuration file path') args = parser.parse_args()

  2. Handles FastDeploy Config Structure python config = json.load(open(args.config)) deployment_id = config.get('deployment_id') service_name = config.get('deploy_script', '').split('/')[0]

  3. Outputs JSON Status ```python def emit_step(name, state, message=""): step_data = {"name": name, "state": state} if message: step_data["message"] = message print(json.dumps(step_data), flush=True)

# States: "running", "success", "failure" emit_step("Install packages", "running", "Installing dependencies...") emit_step("Install packages", "success", "All packages installed") ```

  1. Returns Exit Code
  2. Return 0 for success
  3. Return non-zero for failure
  4. Handle exceptions gracefully

Security Model

FastDeploy uses a multi-user security model:

Web Request → FastDeploy (fastdeploy user)
    ↓
    Creates secure config in /var/tmp/
    ↓
    sudo → deploy.py (deploy user)
    ↓
    - Clones ops-control
    - Runs ansible-playbook
    - Has access to SOPS keys

Key security features: - User isolation: FastDeploy runs as fastdeploy, deployments as deploy - Sudo restrictions: Specific sudoers rules per service - Config security: Temporary config files with 0o640 permissions - Secrets management: SOPS-encrypted secrets, keys only readable by deploy user - No network privileges: Deploy user cannot make outbound connections

Troubleshooting

Common Issues

  1. Service not appearing in UI
  2. Check config.json exists in /home/fastdeploy/site/services/
  3. Run python commands.py syncservices to refresh
  4. Verify API token is valid

  5. Deployment fails with permission denied

  6. Check sudoers rule exists in /etc/sudoers.d/fastdeploy_<service>
  7. Verify deploy user exists and has correct permissions
  8. Check config file permissions (should be 0o640)

  9. "deployments succeed but no steps execute"

  10. Often caused by systemd hardening features
  11. Check NoNewPrivileges=false and PrivateTmp=false in service
  12. See systemd-hardening.md for details

  13. Script errors with 'service_name' KeyError

  14. Extract service name from deploy_script field, not service_name
  15. Use: service_name = config.get('deploy_script', '').split('/')[0]

Debugging Steps

  1. Check FastDeploy logs: journalctl -u fastdeploy -f
  2. Test deployment script manually: bash sudo -u deploy /home/deploy/runners/<service>/deploy.py --config /path/to/config.json
  3. Verify database state: Check deployments and steps tables
  4. Test service registration: python commands.py syncservices

Development Workflow

  1. Test locally: Use just deploy-one fastdeploy for development
  2. Register service: Create registration playbook and run just register-one <service>
  3. Test deployment: Use FastDeploy web UI or test script
  4. Iterate: Make changes and re-deploy as needed
  5. Production: Register FastDeploy for self-deployment when ready

This workflow allows rapid development while maintaining production deployment capabilities.