diff --git a/roles/coolify/defaults/main.yaml b/roles/coolify/defaults/main.yaml index f578b8a..3ad3f83 100644 --- a/roles/coolify/defaults/main.yaml +++ b/roles/coolify/defaults/main.yaml @@ -1,32 +1,56 @@ --- -coolify_state: present # latest | present | absent +# present - Install if missing, ensure running if already installed +# latest - Install if missing, update to latest if already installed +# absent - Uninstall Coolify (see coolify_remove_data_on_absent) +# stopped - Stop Coolify services without removing +coolify_state: present + coolify_remove_data_on_absent: false + coolify_base_dir: /data/coolify -coolify_docker_network: coolify -coolify_http_port: 8000 - -coolify_owner: 9999 +coolify_owner: "9999" coolify_group: root + coolify_compose_files: - docker-compose.yml - docker-compose.prod.yml +coolify_docker_network: coolify + +# Used for health checks and cleanup coolify_container_names: - coolify - coolify-db - coolify-redis - coolify-realtime +coolify_registry_url: "ghcr.io" +coolify_image_tag: "latest" + +# Auto-generated on first install if left empty coolify_app_id: "" coolify_app_name: "Coolify" coolify_app_key: "" +coolify_app_env: "production" +coolify_app_port: 8000 + +coolify_php_memory_limit: "256M" +coolify_php_fpm_pm_control: "dynamic" +coolify_php_fpm_pm_start_servers: 1 +coolify_php_fpm_pm_min_spare_servers: 1 +coolify_php_fpm_pm_max_spare_servers: 10 coolify_db_username: "coolify" coolify_db_password: "" +coolify_db_database: "coolify" coolify_redis_password: "" +coolify_soketi_port: 6001 +coolify_soketi_debug: "false" + +# Auto-generated on first install if left empty coolify_pusher_app_id: "" coolify_pusher_app_key: "" coolify_pusher_app_secret: "" @@ -35,4 +59,6 @@ coolify_root_username: "" coolify_root_user_email: "" coolify_root_user_password: "" -coolify_registry_url: "ghcr.io" +coolify_health_retries: 20 +coolify_health_delay: 15 +coolify_compose_wait_timeout: 300 diff --git a/roles/coolify/tasks/configure.yaml b/roles/coolify/tasks/configure.yaml index 58bf825..1f90dd7 100644 --- a/roles/coolify/tasks/configure.yaml +++ b/roles/coolify/tasks/configure.yaml @@ -1,5 +1,6 @@ --- - name: Ensure source directory exists + become: true ansible.builtin.file: path: "{{ coolify_base_dir }}/source" state: directory @@ -8,31 +9,72 @@ mode: "0750" tags: coolify, configuration -- name: Check if .env exists +- name: Check if .env file already exists ansible.builtin.stat: path: "{{ coolify_base_dir }}/source/.env" - register: coolify_env_file + register: _coolify_env_check tags: coolify, configuration -- name: Generate missing secrets (only for new install) - ansible.builtin.set_fact: - coolify_app_key: "{{ coolify_app_key | default(lookup('password', '/dev/null length=32 chars=ascii_letters,digits'), true) }}" - coolify_db_password: "{{ coolify_db_password | default(lookup('password', '/dev/null length=32 chars=ascii_letters,digits'), true) }}" - coolify_redis_password: "{{ coolify_redis_password | default(lookup('password', '/dev/null length=32 chars=ascii_letters,digits'), true) }}" - coolify_pusher_app_key: "{{ coolify_pusher_app_key | default(lookup('password', '/dev/null length=32 chars=ascii_letters,digits'), true) }}" - coolify_pusher_app_secret: "{{ coolify_pusher_app_secret | default(lookup('password', '/dev/null length=32 chars=ascii_letters,digits'), true) }}" - coolify_app_id: "{{ coolify_app_id | default(lookup('password', '/dev/null length=16 chars=ascii_letters,digits'), true) }}" - coolify_pusher_app_id: "{{ coolify_pusher_app_id | default(lookup('password', '/dev/null length=16 chars=ascii_letters,digits'), true) }}" - when: not coolify_env_file.stat.exists +# Empty strings in defaults/main.yaml signal "please auto-generate" +- name: Generate secrets for initial installation + when: not _coolify_env_check.stat.exists tags: coolify, configuration + block: + - name: Generate APP_ID (if not provided) + ansible.builtin.set_fact: + coolify_app_id: "{{ lookup('password', '/dev/null length=16 chars=ascii_letters,digits') }}" + when: coolify_app_id | default('', true) | length == 0 -- name: Configure .env file + - name: Generate APP_KEY (if not provided) + ansible.builtin.set_fact: + coolify_app_key: "base64:{{ lookup('password', '/dev/null length=32 chars=ascii_letters,digits') | b64encode }}" + when: coolify_app_key | default('', true) | length == 0 + + - name: Generate DB_PASSWORD (if not provided) + ansible.builtin.set_fact: + coolify_db_password: "{{ lookup('password', '/dev/null length=32 chars=ascii_letters,digits') }}" + when: coolify_db_password | default('', true) | length == 0 + + - name: Generate REDIS_PASSWORD (if not provided) + ansible.builtin.set_fact: + coolify_redis_password: "{{ lookup('password', '/dev/null length=32 chars=ascii_letters,digits') }}" + when: coolify_redis_password | default('', true) | length == 0 + + - name: Generate PUSHER_APP_ID (if not provided) + ansible.builtin.set_fact: + coolify_pusher_app_id: "{{ lookup('password', '/dev/null length=16 chars=ascii_letters,digits') }}" + when: coolify_pusher_app_id | default('', true) | length == 0 + + - name: Generate PUSHER_APP_KEY (if not provided) + ansible.builtin.set_fact: + coolify_pusher_app_key: "{{ lookup('password', '/dev/null length=32 chars=ascii_letters,digits') }}" + when: coolify_pusher_app_key | default('', true) | length == 0 + + - name: Generate PUSHER_APP_SECRET (if not provided) + ansible.builtin.set_fact: + coolify_pusher_app_secret: "{{ lookup('password', '/dev/null length=32 chars=ascii_letters,digits') }}" + when: coolify_pusher_app_secret | default('', true) | length == 0 + +- name: Deploy Coolify .env configuration + become: true ansible.builtin.template: src: .env.j2 dest: "{{ coolify_base_dir }}/source/.env" owner: "{{ coolify_owner }}" group: "{{ coolify_group }}" mode: "0640" - force: false - when: not coolify_env_file.stat.exists + backup: true + register: _coolify_env_deployed + notify: + - Restart coolify services + - Wait for coolify health + tags: coolify, configuration + +- name: Show configuration status + ansible.builtin.debug: + msg: >- + .env file {{ 'created' if not _coolify_env_check.stat.exists else + ('updated' if _coolify_env_deployed.changed else 'unchanged') }} + at {{ coolify_base_dir }}/source/.env + verbosity: 1 tags: coolify, configuration diff --git a/roles/coolify/tasks/delete.yaml b/roles/coolify/tasks/delete.yaml index 5c34add..6f5e3f1 100644 --- a/roles/coolify/tasks/delete.yaml +++ b/roles/coolify/tasks/delete.yaml @@ -1,114 +1,131 @@ --- -- name: Check if Coolify is running before uninstallation - community.docker.docker_container_info: - name: "{{ item }}" - loop: "{{ coolify_container_names }}" - register: coolify_containers_pre_uninstall - ignore_errors: true - tags: coolify, deletion, pre-check +- name: Stop Coolify services gracefully + become: true + community.docker.docker_compose_v2: + project_src: "{{ coolify_base_dir }}/source" + files: "{{ coolify_compose_files }}" + state: absent + remove_orphans: true + register: _coolify_compose_down + failed_when: + - _coolify_compose_down.rc is defined + - _coolify_compose_down.rc != 0 + - "'not found' not in (_coolify_compose_down.msg | default(''))" + - "'no such file' not in (_coolify_compose_down.msg | default('') | lower)" + when: _coolify_installed | bool + tags: coolify, deletion -- name: Stop Coolify services - ansible.builtin.include_tasks: stop.yaml - ignore_errors: true - tags: coolify, deletion, stop - -- name: Force remove any remaining Coolify containers +- name: Force-remove any remaining Coolify containers + become: true community.docker.docker_container: - name: "{{ item.item }}" + name: "{{ item }}" state: absent force_kill: true - force_remove: true - loop: "{{ coolify_containers_pre_uninstall.results }}" - when: - - item.exists | default(false) - - coolify_remove_data_on_absent | bool - ignore_errors: true + loop: "{{ coolify_container_names }}" + register: _coolify_remove_containers + failed_when: + - _coolify_remove_containers is failed + - "'No such container' not in (_coolify_remove_containers.msg | default(''))" tags: coolify, docker, deletion -- name: Remove Coolify files and data (conditional) +- name: Remove Coolify Docker volumes + become: true + community.docker.docker_volume: + name: "{{ item }}" + state: absent + loop: + - coolify-db + - coolify-redis + register: _coolify_remove_volumes + failed_when: + - _coolify_remove_volumes is failed + - "'no such volume' not in (_coolify_remove_volumes.msg | default('') | lower)" + - "'not found' not in (_coolify_remove_volumes.msg | default('') | lower)" + when: coolify_remove_data_on_absent | bool + tags: coolify, docker, deletion + +- name: Remove Coolify source files (always on absent) + become: true + ansible.builtin.file: + path: "{{ coolify_base_dir }}/source" + state: absent + tags: coolify, files, deletion + +- name: Remove Coolify data directories + become: true ansible.builtin.file: path: "{{ item }}" state: absent loop: - - "{{ coolify_base_dir }}/source" + - "{{ coolify_base_dir }}/ssh" - "{{ coolify_base_dir }}/applications" - "{{ coolify_base_dir }}/databases" - "{{ coolify_base_dir }}/backups" - "{{ coolify_base_dir }}/services" - "{{ coolify_base_dir }}/proxy" - "{{ coolify_base_dir }}/webhooks-during-maintenance" - - "{{ coolify_base_dir }}/ssh" - "{{ coolify_base_dir }}/sentinel" when: coolify_remove_data_on_absent | bool - tags: coolify, files, deletion, data + tags: coolify, files, deletion -- name: Remove base directory if empty (only when requested) +- name: Remove Coolify base directory (if full removal requested) + become: true ansible.builtin.file: path: "{{ coolify_base_dir }}" state: absent when: coolify_remove_data_on_absent | bool - tags: coolify, files, deletion, data + tags: coolify, files, deletion -- name: Remove Coolify docker volumes (find) - community.docker.docker_volume_info: - name: "^coolify_.*" - register: coolify_volumes - ignore_errors: true - when: coolify_remove_data_on_absent | bool - tags: coolify, docker, deletion - -- name: Remove Coolify docker volumes - community.docker.docker_volume: - name: "{{ item.Name }}" - state: absent - force: true - loop: "{{ coolify_volumes.volumes | default([]) }}" - when: coolify_volumes is defined and coolify_volumes.volumes | length > 0 - tags: coolify, docker, deletion - -- name: Remove Coolify docker network +- name: Remove Coolify Docker network + become: true community.docker.docker_network: name: "{{ coolify_docker_network }}" state: absent - force: "{{ coolify_remove_data_on_absent | bool }}" - ignore_errors: true + force: true + register: _coolify_remove_network + failed_when: + - _coolify_remove_network is failed + - "'not found' not in (_coolify_remove_network.msg | default('') | lower)" + - "'network coolify not found' not in (_coolify_remove_network.msg | default('') | lower)" when: coolify_remove_data_on_absent | bool tags: coolify, docker, deletion -- name: Prune Coolify images +- name: Prune Coolify container images become: true community.docker.docker_prune: images: true images_filters: reference: "ghcr.io/coollabsio/*" - build_cache: true - ignore_errors: true + register: _coolify_prune_images + failed_when: + - _coolify_prune_images is failed + - "'permission denied' in (_coolify_prune_images.msg | default('') | lower)" when: coolify_remove_data_on_absent | bool tags: coolify, docker, deletion -- name: Prune unused Docker resources - become: true - community.docker.docker_prune: - containers: true - images: false - networks: true - volumes: false - builder_cache: true - ignore_errors: true - when: coolify_remove_data_on_absent | bool - tags: coolify, docker, deletion - -- name: Verify Coolify removal +- name: Verify Coolify containers are removed community.docker.docker_container_info: name: "{{ item }}" loop: "{{ coolify_container_names }}" - register: coolify_containers_post_uninstall - ignore_errors: true - tags: coolify, deletion, verification + register: _coolify_post_delete + failed_when: false + tags: coolify, deletion -- name: Display uninstallation message +- name: Confirm containers are gone + ansible.builtin.assert: + that: + - not (item.exists | default(false)) + fail_msg: "Container {{ item.item }} still exists after uninstall" + quiet: true + loop: "{{ _coolify_post_delete.results }}" + loop_control: + label: "{{ item.item }}" + failed_when: false + tags: coolify, deletion + +- name: Show uninstallation summary ansible.builtin.debug: msg: - "Coolify has been uninstalled" - - "Application data preserved: {{ not coolify_remove_data_on_absent }}" + - "Data removed: {{ coolify_remove_data_on_absent | bool }}" tags: coolify, deletion diff --git a/roles/coolify/tasks/detect.yaml b/roles/coolify/tasks/detect.yaml index 2a45849..5979bc4 100644 --- a/roles/coolify/tasks/detect.yaml +++ b/roles/coolify/tasks/detect.yaml @@ -1,20 +1,75 @@ --- -- name: Check if Coolify is installed (compose file exists) +- name: Check if Coolify compose file exists ansible.builtin.stat: path: "{{ coolify_base_dir }}/source/{{ coolify_compose_files[0] }}" - register: coolify_installed + register: _coolify_compose_stat + tags: always + +- name: Set installation fact + ansible.builtin.set_fact: + _coolify_installed: "{{ _coolify_compose_stat.stat.exists }}" + tags: always + +- name: Check if .env file exists + ansible.builtin.stat: + path: "{{ coolify_base_dir }}/source/.env" + register: _coolify_env_stat + tags: always + +- name: Set env file fact + ansible.builtin.set_fact: + _coolify_env_exists: "{{ _coolify_env_stat.stat.exists }}" tags: always - name: Check Coolify container status community.docker.docker_container_info: name: "{{ item }}" loop: "{{ coolify_container_names }}" - register: coolify_containers - when: coolify_installed.stat.exists + register: _coolify_containers + failed_when: false + when: _coolify_installed | bool tags: always -- name: Set Coolify health fact +- name: Determine Coolify health status ansible.builtin.set_fact: - coolify_healthy: "{{ coolify_containers.results | selectattr('container', 'defined') | selectattr('container.State.Health.Status', 'defined') | selectattr('container.State.Health.Status', 'equalto', 'healthy') | list | length == coolify_container_names | length }}" - when: coolify_containers is defined and coolify_containers.results is defined + _coolify_healthy: >- + {{ + _coolify_containers.results + | default([]) + | selectattr('exists', 'defined') + | selectattr('exists', 'equalto', true) + | selectattr('container', 'defined') + | map(attribute='container') + | selectattr('State.Health', 'defined') + | selectattr('State.Health.Status', 'equalto', 'healthy') + | list + | length == coolify_container_names | length + }} + _coolify_running: >- + {{ + _coolify_containers.results + | default([]) + | selectattr('exists', 'defined') + | selectattr('exists', 'equalto', true) + | list + | length == coolify_container_names | length + }} + when: _coolify_installed | bool + tags: always + +- name: Set default health facts when not installed + ansible.builtin.set_fact: + _coolify_healthy: false + _coolify_running: false + when: not (_coolify_installed | bool) + tags: always + +- name: Display detected state + ansible.builtin.debug: + msg: + - "Installed: {{ _coolify_installed }}" + - "Env exists: {{ _coolify_env_exists }}" + - "Running: {{ _coolify_running }}" + - "Healthy: {{ _coolify_healthy }}" + verbosity: 1 tags: always diff --git a/roles/coolify/tasks/install.yaml b/roles/coolify/tasks/install.yaml index 0779444..e2a9895 100644 --- a/roles/coolify/tasks/install.yaml +++ b/roles/coolify/tasks/install.yaml @@ -1,5 +1,6 @@ --- - name: Install prerequisites (apt) + become: true ansible.builtin.apt: name: - curl @@ -10,13 +11,15 @@ tags: coolify, prerequisites, installation - name: Ensure Docker service is started and enabled + become: true ansible.builtin.systemd: name: docker state: started enabled: true tags: coolify, docker, installation -- name: Create Coolify directories +- name: Create Coolify directory structure + become: true ansible.builtin.file: path: "{{ item }}" state: directory @@ -35,40 +38,34 @@ - "{{ coolify_base_dir }}/webhooks-during-maintenance" tags: coolify, files, installation -- name: Download Coolify configuration files - ansible.builtin.get_url: - url: "https://cdn.coollabs.io/coolify/{{ item.file }}" +- name: Deploy Coolify compose files from templates + become: true + ansible.builtin.template: + src: "{{ item.src }}" dest: "{{ coolify_base_dir }}/source/{{ item.dest }}" - mode: "0644" owner: "{{ coolify_owner }}" group: "{{ coolify_group }}" + mode: "0644" loop: - - { file: "docker-compose.yml", dest: "docker-compose.yml" } - - { file: "docker-compose.prod.yml", dest: "docker-compose.prod.yml" } - - { file: "upgrade.sh", dest: "upgrade.sh" } + - src: docker-compose.yml.j2 + dest: docker-compose.yml + - src: docker-compose.prod.yml.j2 + dest: docker-compose.prod.yml + loop_control: + label: "{{ item.dest }}" tags: coolify, files, installation - name: Configure Coolify environment ansible.builtin.include_tasks: configure.yaml tags: coolify, configuration, installation -- name: Ensure correct ownership and permissions recursively - ansible.builtin.file: - path: "{{ coolify_base_dir }}" - owner: "{{ coolify_owner }}" - group: "{{ coolify_group }}" - mode: "0750" - recurse: true - tags: coolify, permissions, installation - -- name: Start Coolify +- name: Start Coolify services ansible.builtin.include_tasks: start.yaml tags: coolify, start, installation -- name: Show installed message +- name: Show installation success message ansible.builtin.debug: msg: - "Coolify installed successfully" - - "All containers are healthy and responding" - - "Access at: http://{{ ansible_host }}:{{ coolify_http_port }}" + - "Access at: http://{{ ansible_host }}:{{ coolify_app_port }}" tags: coolify, installation diff --git a/roles/coolify/tasks/main.yaml b/roles/coolify/tasks/main.yaml index 9f8e469..05114c2 100644 --- a/roles/coolify/tasks/main.yaml +++ b/roles/coolify/tasks/main.yaml @@ -3,66 +3,68 @@ ansible.builtin.assert: that: - coolify_state in ['present', 'absent', 'latest', 'stopped'] - msg: "coolify_state must be one of: present, absent, latest, stopped" + fail_msg: >- + Invalid coolify_state '{{ coolify_state }}'. + Must be one of: present, absent, latest, stopped. + quiet: true tags: always - name: Detect Coolify installation status ansible.builtin.include_tasks: detect.yaml tags: always -- name: Ensure Docker network exists - become: true - community.docker.docker_network: - name: "{{ coolify_docker_network }}" - driver: bridge - attachable: true - state: present - tags: coolify, docker, installation - - name: Uninstall Coolify ansible.builtin.include_tasks: delete.yaml when: coolify_state == 'absent' tags: coolify, deletion -- name: Install Coolify - ansible.builtin.include_tasks: install.yaml - when: - - coolify_state in ['present', 'latest'] - - not coolify_installed.stat.exists - tags: coolify, installation - -- name: Update Coolify - ansible.builtin.include_tasks: update.yaml - when: - - coolify_state == 'latest' - - coolify_installed.stat.exists - tags: coolify, update - -- name: Ensure Configuration - ansible.builtin.include_tasks: configure.yaml - when: - - coolify_state == 'present' - - coolify_installed.stat.exists - tags: coolify, configuration - -- name: Ensure Started - ansible.builtin.include_tasks: start.yaml - when: - - coolify_state == 'present' - - coolify_installed.stat.exists - tags: coolify, start - - name: Stop Coolify ansible.builtin.include_tasks: stop.yaml when: - coolify_state == 'stopped' - - coolify_installed.stat.exists + - _coolify_installed tags: coolify, stop -- name: Show status +- name: Ensure Docker network exists + become: true + community.docker.docker_network: + name: "{{ coolify_docker_network }}" + driver: bridge + state: present + when: coolify_state in ['present', 'latest'] + tags: coolify, docker + +- name: Install Coolify (fresh) + ansible.builtin.include_tasks: install.yaml + when: + - coolify_state in ['present', 'latest'] + - not _coolify_installed + tags: coolify, installation + +- name: Update Coolify (existing) + ansible.builtin.include_tasks: update.yaml + when: + - coolify_state == 'latest' + - _coolify_installed + tags: coolify, update + +- name: Ensure Coolify is configured and running + when: + - coolify_state == 'present' + - _coolify_installed + tags: coolify, configuration + block: + - name: Ensure configuration is up to date + ansible.builtin.include_tasks: configure.yaml + + - name: Ensure services are running + ansible.builtin.include_tasks: start.yaml + +- name: Show Coolify status ansible.builtin.debug: msg: - - "Coolify State: {{ coolify_state }}" - - "URL: http://{{ ansible_host }}:{{ coolify_http_port }}" + - "State: {{ coolify_state }}" + - "Installed: {{ _coolify_installed }}" + - "URL: http://{{ ansible_host }}:{{ coolify_app_port }}" when: coolify_state in ['present', 'latest'] tags: coolify, status diff --git a/roles/coolify/tasks/start.yaml b/roles/coolify/tasks/start.yaml index d9ba140..03ceb94 100644 --- a/roles/coolify/tasks/start.yaml +++ b/roles/coolify/tasks/start.yaml @@ -1,24 +1,23 @@ --- -- name: Start Coolify services +- name: Start Coolify services via Docker Compose become: true community.docker.docker_compose_v2: project_src: "{{ coolify_base_dir }}/source" files: "{{ coolify_compose_files }}" - pull: always state: present + pull: always wait: true - wait_timeout: 300 + wait_timeout: "{{ coolify_compose_wait_timeout }}" tags: coolify, docker, start - name: Wait for Coolify HTTP to respond ansible.builtin.uri: - url: "http://localhost:{{ coolify_http_port }}" + url: "http://localhost:{{ coolify_app_port }}" method: GET status_code: 200 timeout: 30 - body_format: json - register: coolify_health - until: coolify_health.status == 200 - retries: 10 - delay: 10 + register: _coolify_health + until: _coolify_health.status == 200 + retries: "{{ coolify_health_retries }}" + delay: "{{ coolify_health_delay }}" tags: coolify, health, start diff --git a/roles/coolify/tasks/stop.yaml b/roles/coolify/tasks/stop.yaml index e157b58..dd4fdda 100644 --- a/roles/coolify/tasks/stop.yaml +++ b/roles/coolify/tasks/stop.yaml @@ -1,10 +1,34 @@ --- -- name: Stop Coolify services (compose down) +- name: Stop Coolify services via Docker Compose become: true community.docker.docker_compose_v2: project_src: "{{ coolify_base_dir }}/source" files: "{{ coolify_compose_files }}" - state: absent + state: stopped remove_orphans: true - remove_volumes: false tags: coolify, docker, stop + +- name: Verify Coolify containers are stopped + community.docker.docker_container_info: + name: "{{ item }}" + loop: "{{ coolify_container_names }}" + register: _coolify_stopped_check + failed_when: false + tags: coolify, docker, stop + +- name: Confirm all containers are stopped + ansible.builtin.assert: + that: + - not (item.exists | default(false)) or + (item.container.State.Running | default(false)) == false + fail_msg: "Container {{ item.item }} is still running" + quiet: true + loop: "{{ _coolify_stopped_check.results }}" + loop_control: + label: "{{ item.item }}" + tags: coolify, docker, stop + +- name: Show stop confirmation + ansible.builtin.debug: + msg: "Coolify services have been stopped. Data and volumes are preserved." + tags: coolify, stop diff --git a/roles/coolify/tasks/update.yaml b/roles/coolify/tasks/update.yaml index c05e0d4..5807b34 100644 --- a/roles/coolify/tasks/update.yaml +++ b/roles/coolify/tasks/update.yaml @@ -1,22 +1,27 @@ --- -- name: Update Coolify configuration files - ansible.builtin.get_url: - url: "https://cdn.coollabs.io/coolify/{{ item.file }}" +- name: Deploy latest Coolify compose files from templates + become: true + ansible.builtin.template: + src: "{{ item.src }}" dest: "{{ coolify_base_dir }}/source/{{ item.dest }}" - mode: "0644" owner: "{{ coolify_owner }}" group: "{{ coolify_group }}" + mode: "0644" loop: - - { file: "docker-compose.yml", dest: "docker-compose.yml" } - - { file: "docker-compose.prod.yml", dest: "docker-compose.prod.yml" } - - { file: "upgrade.sh", dest: "upgrade.sh" } + - src: docker-compose.yml.j2 + dest: docker-compose.yml + - src: docker-compose.prod.yml.j2 + dest: docker-compose.prod.yml + loop_control: + label: "{{ item.dest }}" + register: _coolify_compose_updated tags: coolify, files, update -- name: Ensure Coolify environment configuration exists +- name: Ensure Coolify environment configuration is current ansible.builtin.include_tasks: configure.yaml tags: coolify, configuration, update -- name: Update Coolify services (pull and recreate) +- name: Pull latest Coolify images and recreate services become: true community.docker.docker_compose_v2: project_src: "{{ coolify_base_dir }}/source" @@ -25,26 +30,25 @@ state: present recreate: always wait: true - wait_timeout: 300 + wait_timeout: "{{ coolify_compose_wait_timeout }}" + register: _coolify_update_result tags: coolify, docker, update - name: Wait for Coolify HTTP to respond after update ansible.builtin.uri: - url: "http://localhost:{{ coolify_http_port }}" + url: "http://localhost:{{ coolify_app_port }}" method: GET status_code: 200 timeout: 30 - body_format: json - register: coolify_health_after_update - until: coolify_health_after_update.status == 200 - retries: 20 - delay: 15 + register: _coolify_health_after_update + until: _coolify_health_after_update.status == 200 + retries: "{{ coolify_health_retries }}" + delay: "{{ coolify_health_delay }}" tags: coolify, update, health -- name: Show update success message +- name: Show update result ansible.builtin.debug: msg: - "Coolify updated successfully to latest version" - - "Services have been recreated with latest images and config" - - "Access at: http://{{ ansible_host }}:{{ coolify_http_port }}" + - "Access at: http://{{ ansible_host }}:{{ coolify_app_port }}" tags: coolify, update diff --git a/roles/coolify/templates/.env.j2 b/roles/coolify/templates/.env.j2 index fc5cf40..1d0c675 100644 --- a/roles/coolify/templates/.env.j2 +++ b/roles/coolify/templates/.env.j2 @@ -1,18 +1,38 @@ -APP_ID={{ coolify_app_id | default('') }} -APP_NAME={{ coolify_app_name | default('Coolify') }} -APP_KEY={{ coolify_app_key | default('') }} +# Managed by Ansible — manual changes will be overwritten on next deployment -DB_USERNAME={{ coolify_db_username | default('coolify') }} -DB_PASSWORD={{ coolify_db_password | default('') }} +APP_ID={{ coolify_app_id }} +APP_NAME={{ coolify_app_name }} +APP_KEY={{ coolify_app_key }} +APP_ENV={{ coolify_app_env }} +APP_PORT={{ coolify_app_port }} -REDIS_PASSWORD={{ coolify_redis_password | default('') }} +REGISTRY_URL={{ coolify_registry_url }} +LATEST_IMAGE={{ coolify_image_tag }} -PUSHER_APP_ID={{ coolify_pusher_app_id | default('') }} -PUSHER_APP_KEY={{ coolify_pusher_app_key | default('') }} -PUSHER_APP_SECRET={{ coolify_pusher_app_secret | default('') }} +PHP_MEMORY_LIMIT={{ coolify_php_memory_limit }} +PHP_FPM_PM_CONTROL={{ coolify_php_fpm_pm_control }} +PHP_FPM_PM_START_SERVERS={{ coolify_php_fpm_pm_start_servers }} +PHP_FPM_PM_MIN_SPARE_SERVERS={{ coolify_php_fpm_pm_min_spare_servers }} +PHP_FPM_PM_MAX_SPARE_SERVERS={{ coolify_php_fpm_pm_max_spare_servers }} -ROOT_USERNAME={{ coolify_root_username | default('') }} -ROOT_USER_EMAIL={{ coolify_root_user_email | default('') }} -ROOT_USER_PASSWORD={{ coolify_root_user_password | default('') }} +DB_USERNAME={{ coolify_db_username }} +DB_PASSWORD={{ coolify_db_password }} +DB_DATABASE={{ coolify_db_database }} -REGISTRY_URL={{ coolify_registry_url | default('ghcr.io') }} +REDIS_PASSWORD={{ coolify_redis_password }} + +SOKETI_PORT={{ coolify_soketi_port }} +SOKETI_DEBUG={{ coolify_soketi_debug }} +PUSHER_APP_ID={{ coolify_pusher_app_id }} +PUSHER_APP_KEY={{ coolify_pusher_app_key }} +PUSHER_APP_SECRET={{ coolify_pusher_app_secret }} + +{% if coolify_root_username | default('', true) | length > 0 %} +ROOT_USERNAME={{ coolify_root_username }} +{% endif %} +{% if coolify_root_user_email | default('', true) | length > 0 %} +ROOT_USER_EMAIL={{ coolify_root_user_email }} +{% endif %} +{% if coolify_root_user_password | default('', true) | length > 0 %} +ROOT_USER_PASSWORD={{ coolify_root_user_password }} +{% endif %} diff --git a/roles/coolify/templates/docker-compose.prod.yml.j2 b/roles/coolify/templates/docker-compose.prod.yml.j2 new file mode 100644 index 0000000..b28ee66 --- /dev/null +++ b/roles/coolify/templates/docker-compose.prod.yml.j2 @@ -0,0 +1,104 @@ +# Managed by Ansible — manual changes will be overwritten on next deployment +services: + coolify: + image: "${REGISTRY_URL:-ghcr.io}/coollabsio/coolify:${LATEST_IMAGE:-latest}" + volumes: + - type: bind + source: /data/coolify/source/.env + target: /var/www/html/.env + read_only: true + - /data/coolify/ssh:/var/www/html/storage/app/ssh + - /data/coolify/applications:/var/www/html/storage/app/applications + - /data/coolify/databases:/var/www/html/storage/app/databases + - /data/coolify/services:/var/www/html/storage/app/services + - /data/coolify/backups:/var/www/html/storage/app/backups + environment: + - APP_ENV=${APP_ENV:-production} + - PHP_MEMORY_LIMIT=${PHP_MEMORY_LIMIT:-256M} + - PHP_FPM_PM_CONTROL=${PHP_FPM_PM_CONTROL:-dynamic} + - PHP_FPM_PM_START_SERVERS=${PHP_FPM_PM_START_SERVERS:-1} + - PHP_FPM_PM_MIN_SPARE_SERVERS=${PHP_FPM_PM_MIN_SPARE_SERVERS:-1} + - PHP_FPM_PM_MAX_SPARE_SERVERS=${PHP_FPM_PM_MAX_SPARE_SERVERS:-10} + env_file: + - /data/coolify/source/.env + ports: + - name: web + target: 8080 + published: ${APP_PORT:-8000} + host_ip: 127.0.0.1 + protocol: tcp + app_protocol: http + expose: + - "${APP_PORT:-8000}" + healthcheck: + test: curl --fail http://127.0.0.1:8080/api/health || exit 1 + interval: 5s + retries: 10 + timeout: 2s + + postgres: + volumes: + - coolify-db:/var/lib/postgresql/data + environment: + POSTGRES_USER: "${DB_USERNAME}" + POSTGRES_PASSWORD: "${DB_PASSWORD}" + POSTGRES_DB: "${DB_DATABASE:-coolify}" + healthcheck: + test: + - CMD-SHELL + - pg_isready -U ${DB_USERNAME} -d ${DB_DATABASE:-coolify} + interval: 5s + retries: 10 + timeout: 2s + redis: + command: redis-server --save 20 1 --loglevel warning --requirepass ${REDIS_PASSWORD} + environment: + REDIS_PASSWORD: "${REDIS_PASSWORD}" + volumes: + - coolify-redis:/data + healthcheck: + test: redis-cli ping + interval: 5s + retries: 10 + timeout: 2s + + soketi: + image: "${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-realtime:1.0.10" + ports: + - name: soketi + target: 6001 + published: ${SOKETI_PORT:-6001} + host_ip: 127.0.0.1 + protocol: tcp + app_protocol: http + - name: terminal + target: 6002 + published: 6002 + host_ip: 127.0.0.1 + protocol: tcp + app_protocol: http + volumes: + - /data/coolify/ssh:/var/www/html/storage/app/ssh + environment: + APP_NAME: "${APP_NAME:-Coolify}" + SOKETI_DEBUG: "${SOKETI_DEBUG:-false}" + SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID}" + SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}" + SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}" + healthcheck: + test: + - CMD-SHELL + - wget -qO- http://127.0.0.1:6001/ready && wget -qO- http://127.0.0.1:6002/ready || exit 1 + interval: 5s + retries: 10 + timeout: 2s + +volumes: + coolify-db: + name: coolify-db + coolify-redis: + name: coolify-redis + +networks: + coolify: + external: true diff --git a/roles/coolify/templates/docker-compose.yml.j2 b/roles/coolify/templates/docker-compose.yml.j2 new file mode 100644 index 0000000..fc423e0 --- /dev/null +++ b/roles/coolify/templates/docker-compose.yml.j2 @@ -0,0 +1,41 @@ +# Managed by Ansible — manual changes will be overwritten on next deployment +services: + coolify: + container_name: coolify + restart: always + working_dir: /var/www/html + extra_hosts: + - host.docker.internal:host-gateway + networks: + - coolify + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + soketi: + condition: service_healthy + postgres: + image: postgres:15-alpine + container_name: coolify-db + restart: always + networks: + - coolify + redis: + image: redis:7-alpine + container_name: coolify-redis + restart: always + networks: + - coolify + soketi: + container_name: coolify-realtime + extra_hosts: + - host.docker.internal:host-gateway + restart: always + networks: + - coolify +networks: + coolify: + name: coolify + driver: bridge + external: false