FastDeploy Deploy Role¶
Deploy the FastDeploy web-based deployment platform with full database, frontend, and service management.
Description¶
This role deploys FastDeploy, a web-based platform for managing service deployments. It handles the complete deployment lifecycle including:
PostgreSQL provisioning via the shared
postgres_installrole and application migrationsPython environment management with uv
Frontend application building
Systemd service configuration
Traefik reverse proxy integration
Initial admin user creation
Service synchronization from filesystem
Explicit post-deploy rollout of
fastdeploy.serviceDB-backed post-restart validation against the running FastDeploy process
Requirements¶
Ubuntu/Debian-based target system
Ability to install PostgreSQL (handled automatically via dependency)
Node.js 16+ (for frontend build)
Python 3.14+ (managed via uv; set
fastdeploy_python_versionif needed)ansible-core 2.20+
Required collections:
ansible.posix(for synchronize module)community.general(for PostgreSQL modules)
Role Variables¶
Required Variables¶
These variables MUST be set when using this role:
fastdeploy_source_path: "" # Local path to FastDeploy source code
fastdeploy_secret_key: "" # Django secret key (generate with: openssl rand -hex 32)
fastdeploy_initial_password_hash: "" # BCrypt hash (generate with: python -c "import bcrypt; print(bcrypt.hashpw(b'password', bcrypt.gensalt()).decode())")
fastdeploy_postgres_password: "" # PostgreSQL password for FastDeploy database
Common Configuration¶
Frequently modified settings with sensible defaults:
# API and WebSocket URLs
fastdeploy_api_url: "https://deploy.example.com"
fastdeploy_websocket_url: "wss://deploy.example.com/deployments/ws"
# Initial admin user
fastdeploy_initial_user: "admin"
# Traefik configuration
fastdeploy_traefik_enabled: true
fastdeploy_traefik_host: "deploy.example.com"
# Application settings
fastdeploy_app_port: 9999
fastdeploy_workers: 4
# Python / uv
fastdeploy_python_version: "3.14"
Advanced Configuration¶
# User and paths
fastdeploy_user: "fastdeploy"
fastdeploy_home: "/home/fastdeploy"
fastdeploy_site_path: "{{ fastdeploy_home }}/site"
# Database configuration
fastdeploy_postgres_database: "fastdeploy"
fastdeploy_postgres_user: "fastdeploy"
# Feature flags
fastdeploy_build_frontend: true
fastdeploy_create_initial_user: true
fastdeploy_sync_services: true
# Rollout controls
fastdeploy_rollout_restart_enabled: true
fastdeploy_rollout_validation_enabled: true
fastdeploy_rollout_validation_user: "{{ fastdeploy_initial_user }}"
For a complete list of variables, see defaults/main.yml.
Rollout behavior¶
Every successful role run is treated as a rollout of the live FastDeploy service:
the role explicitly restarts
fastdeploy.servicenear the end of the deploy flowit does not rely on incidental handler notifications from unit-file changes
after the restart, it validates the running process with an authenticated localhost request to
/users/me
That validation is DB-backed and secret-backed in the running service process:
FastDeploy must accept a JWT signed with the configured
SECRET_KEYFastDeploy must successfully load the referenced user from the database
This catches the stale-process class of failures where code, config, or rotated DB credentials were updated on disk but the long-running FastDeploy process did not pick them up.
If you disable initial-user creation or want validation to target a different existing account, set
fastdeploy_rollout_validation_user accordingly.
PostgreSQL Provisioning¶
The role automatically pulls in local.ops_library.postgres_install to install and configure PostgreSQL. Customize the forwarded settings via:
fastdeploy_postgres_version: "17"
fastdeploy_postgres_packages:
- "postgresql-{{ fastdeploy_postgres_version }}"
- "postgresql-client-{{ fastdeploy_postgres_version }}"
- "postgresql-contrib-{{ fastdeploy_postgres_version }}"
- libpq-dev
fastdeploy_postgres_repo_enabled: true
fastdeploy_postgres_repo_url: https://apt.postgresql.org/pub/repos/apt
fastdeploy_postgres_repo_codename: "{{ ansible_distribution_release | default('jammy') }}"
fastdeploy_postgres_repo_components:
- main
fastdeploy_postgres_repo_keyring: /etc/apt/keyrings/postgresql.asc
fastdeploy_postgres_repo_key_url: https://www.postgresql.org/media/keys/ACCC4CF8.asc
The dependency ensures the fastdeploy_postgres_database and fastdeploy_postgres_user are created before the application runs migrations.
Runtime output hardening¶
The role keeps the normal syncservices progress visible, but it intentionally censors the
Create initial admin user task in Ansible output. The upstream commands.py createuser flow can
emit password-hash and database-error context on failure or duplicate-user runs, so the role runs it
via command + environment variables and hides the task result to avoid leaking those values during
FastDeploy-triggered self-deployments.
Traefik Dual Router Authentication¶
The role implements a dual router pattern for security:
Internal router (priority 120): LAN and Tailscale clients bypass basic auth
IP ranges: RFC1918 private networks, Tailscale CGNAT (100.64.0.0/10), Tailscale IPv6 (fd7a::/48)
External router (priority 100): Public internet requires basic auth
Uses shared credentials from
secrets/prod/traefik.yml
This is mandatory for public-facing deployments per security policy.
Configuration:
fastdeploy_basic_auth_enabled: true # Default: true
fastdeploy_basic_auth_user: "admin"
fastdeploy_basic_auth_password: "{{ traefik_secrets.basic_auth_password }}" # Plain text, will be hashed
fastdeploy_internal_ip_ranges: # Customize for your network
- "192.168.0.0/16"
- "100.64.0.0/10"
- "YOUR_IPV6_PREFIX::/64"
Note: The role expects a plain-text password in fastdeploy_basic_auth_password. It will automatically generate the bcrypt hash using htpasswd during deployment. Do NOT provide a pre-hashed password.
Testing:
From LAN (192.168.x): No auth prompt
From Tailscale (100.x): No auth prompt
From public internet: Basic auth prompt appears
Dependencies¶
This role automatically depends on local.ops_library.postgres_install to install PostgreSQL and provision the FastDeploy database/user.
Contributor Notes¶
The deploy helper extraction keeps the public role entrypoint unchanged while
moving the duplicated systemd and Traefik plumbing into the internal helper
role local.ops_library.webapp_deploy_internal. FastDeploy still owns its
role-specific validation, environment setup, templates, and
application initialization flow. The remaining user.yml, source_*, and
python.yml steps are intentionally still local to this role because the
comparison with nyxmon_deploy did not show a narrower stable primitive yet.
Example Playbook¶
Basic Usage¶
- name: Deploy FastDeploy
hosts: production
become: true
vars:
secrets: "{{ lookup('community.sops.sops', 'secrets/prod/fastdeploy.yml') | from_yaml }}"
roles:
- role: local.ops_library.fastdeploy_deploy
vars:
fastdeploy_source_path: "/Users/john/projects/fastdeploy"
fastdeploy_secret_key: "{{ secrets.django_secret_key }}"
fastdeploy_initial_password_hash: "{{ secrets.admin_password_hash }}"
fastdeploy_postgres_password: "{{ secrets.db_password }}"
Advanced Usage with Custom Configuration¶
- name: Deploy FastDeploy with Custom Settings
hosts: production
become: true
vars:
sops_secrets: "{{ lookup('community.sops.sops', 'secrets/prod/fastdeploy.yml') | from_yaml }}"
roles:
- role: local.ops_library.fastdeploy_deploy
vars:
# Required secrets
fastdeploy_source_path: "/Users/john/projects/fastdeploy"
fastdeploy_secret_key: "{{ sops_secrets.django_secret_key }}"
fastdeploy_initial_password_hash: "{{ sops_secrets.admin_password_hash }}"
fastdeploy_postgres_password: "{{ sops_secrets.db_password }}"
# Custom configuration
fastdeploy_app_port: 10000
fastdeploy_workers: 8
fastdeploy_api_url: "https://deploy.internal.example.com"
fastdeploy_websocket_url: "wss://deploy.internal.example.com/deployments/ws"
fastdeploy_traefik_host: "deploy.internal.example.com"
fastdeploy_traefik_cert_resolver: "internal-ca"
Deployment Execution Architecture¶
When FastDeploy triggers a service deployment, the following execution chain occurs:
FastDeploy Service (runs as: fastdeploy)
│
├── sudo -u deploy (via /etc/sudoers.d/fastdeploy)
│ │
│ └── /home/fastdeploy/site/services/<service>/deploy.sh
│ │
│ └── sudo /usr/bin/env ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook ...
│ (via /etc/sudoers.d/apt_upgrade_<service>)
Key Components¶
fastdeploy user: Runs the FastDeploy web service
deploy user: Executes deployment scripts with elevated privileges
Sudoers rules:
/etc/sudoers.d/fastdeploy: Allowsfastdeployto run deploy.sh scripts asdeploy/etc/sudoers.d/apt_upgrade_*: Allowsdeployto run ansible-playbook as root
For Remote Targets¶
When deploying to remote servers (e.g., apt_upgrade_staging):
SSH keys are stored in
/home/deploy/.ssh/The playbook specifies
ansible_ssh_private_key_fileto use these keysThe deploy key’s public key must be authorized on the target server
Troubleshooting¶
If deployments fail, check:
Sudoers:
sudo -l -U fastdeployandsudo -l -U deploySSH keys:
ls -la /home/deploy/.ssh/SSH connectivity:
sudo -u deploy ssh -i /home/deploy/.ssh/id_ed25519 root@<target> echo OKAnsible manually:
sudo -u deploy sudo /usr/bin/env ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook <playbook> -i <host>, -v
Handlers¶
This role provides the following handlers:
restart traefik- Restarts Traefik when the FastDeploy dynamic config changes
Testing¶
# Run role tests
cd /path/to/ops-library
just test-role fastdeploy_deploy
Changelog¶
1.0.0 (2024-09-22): Initial release with rsync deployment support
See CHANGELOG.md for full history
License¶
MIT