diff --git a/infra/README.md b/infra/README.md index cc35726..6e0917d 100644 --- a/infra/README.md +++ b/infra/README.md @@ -169,42 +169,38 @@ docker restart postgres ## Deploy via Ansible (without CI/CD) -Use the `deploy.yml` playbook to deploy from your local machine — no Forgejo runner or CI/CD pipeline needed. This is useful for hotfixes, CI outages, or production servers without Forgejo. +Use these playbooks to deploy from your local machine — no Forgejo runner needed. ```bash cd infra/ansible -# Deploy to test/staging VPS -ansible-playbook playbooks/deploy.yml -i inventory/test.yml --ask-vault-pass +# Deploy both environments (git pull once, then build+deploy each sequentially) +ansible-playbook playbooks/deploy.yml --ask-vault-pass -# Deploy to production -ansible-playbook playbooks/deploy.yml -i inventory/production.yml --ask-vault-pass +# Deploy staging only +ansible-playbook playbooks/deploy-staging.yml --ask-vault-pass + +# Deploy test only +ansible-playbook playbooks/deploy-test.yml --ask-vault-pass ``` -**What it does:** +**Steps executed per environment:** -1. Pulls the latest code from the configured branch (`repo_branch` in inventory) -2. Runs `deploy.sh` for each environment (sequentially to save RAM), which: - - Builds the Docker app image with build-time env vars - - Builds a migration image and runs `npx payload migrate` - - Stops the old container, starts the new one - - Prunes old Docker images +1. Pull latest code from the configured branch (`staging`) +2. Build app Docker image (bakes in `NEXT_PUBLIC_SERVER_URL` and `NEXT_PUBLIC_SITE_ID`) +3. Build migration image and run `npx payload migrate` +4. Stop and remove the old container +5. Start the new container +6. Fix upload volume permissions +7. Prune old Docker images **Deploy a specific branch:** ```bash -ansible-playbook playbooks/deploy.yml -i inventory/test.yml --ask-vault-pass \ - -e repo_branch=feature/my-branch +ansible-playbook playbooks/deploy.yml --ask-vault-pass -e repo_branch=feature/my-branch ``` -**Deploy only one environment** (e.g., just staging): - -```bash -ansible-playbook playbooks/deploy.yml -i inventory/test.yml --ask-vault-pass \ - -e '{"app_environments": [{"name": "staging", "port": 3001}]}' -``` - -> **Note:** The server must already be provisioned with `setup.yml` before using `deploy.yml`. The deploy playbook only pulls code and rebuilds containers — it does not install Docker, Caddy, or PostgreSQL. +> **Note:** The server must already be provisioned with `setup.yml` before deploying. The deploy playbooks only pull code and rebuild containers — they do not install Docker, Caddy, or PostgreSQL. --- diff --git a/infra/ansible/playbooks/deploy-staging.yml b/infra/ansible/playbooks/deploy-staging.yml new file mode 100644 index 0000000..cb49872 --- /dev/null +++ b/infra/ansible/playbooks/deploy-staging.yml @@ -0,0 +1,20 @@ +--- +- name: Deploy staging environment + hosts: all + become: true + + tasks: + - name: Pull {{ repo_branch }} branch + ansible.builtin.git: + repo: "{{ repo_url }}" + dest: "{{ repo_dir }}" + version: "{{ repo_branch }}" + force: true + accept_hostkey: true + + - name: Build and deploy staging + ansible.builtin.include_role: + name: app + tasks_from: deploy_env + vars: + env: "{{ app_environments | selectattr('name', 'equalto', 'staging') | first }}" diff --git a/infra/ansible/playbooks/deploy-test.yml b/infra/ansible/playbooks/deploy-test.yml new file mode 100644 index 0000000..4983c14 --- /dev/null +++ b/infra/ansible/playbooks/deploy-test.yml @@ -0,0 +1,20 @@ +--- +- name: Deploy test environment + hosts: all + become: true + + tasks: + - name: Pull {{ repo_branch }} branch + ansible.builtin.git: + repo: "{{ repo_url }}" + dest: "{{ repo_dir }}" + version: "{{ repo_branch }}" + force: true + accept_hostkey: true + + - name: Build and deploy test + ansible.builtin.include_role: + name: app + tasks_from: deploy_env + vars: + env: "{{ app_environments | selectattr('name', 'equalto', 'test') | first }}" diff --git a/infra/ansible/playbooks/deploy.yml b/infra/ansible/playbooks/deploy.yml index 7d080c4..6904caf 100644 --- a/infra/ansible/playbooks/deploy.yml +++ b/infra/ansible/playbooks/deploy.yml @@ -1,19 +1,37 @@ --- -- name: Deploy app (rebuild + restart) +- name: Pull latest code (shared for both environments) hosts: all become: true tasks: - - name: Pull latest code + - name: Pull {{ repo_branch }} branch ansible.builtin.git: repo: "{{ repo_url }}" dest: "{{ repo_dir }}" version: "{{ repo_branch }}" force: true + accept_hostkey: true - - name: Deploy each environment - ansible.builtin.shell: | - {{ scripts_dir }}/deploy.sh {{ item.name }} {{ item.port }} - loop: "{{ app_environments }}" - loop_control: - label: "{{ item.name }}" +- name: Deploy staging environment + hosts: all + become: true + + tasks: + - name: Build and deploy staging + ansible.builtin.include_role: + name: app + tasks_from: deploy_env + vars: + env: "{{ app_environments | selectattr('name', 'equalto', 'staging') | first }}" + +- name: Deploy test environment + hosts: all + become: true + + tasks: + - name: Build and deploy test + ansible.builtin.include_role: + name: app + tasks_from: deploy_env + vars: + env: "{{ app_environments | selectattr('name', 'equalto', 'test') | first }}" diff --git a/infra/ansible/roles/app/tasks/deploy.yml b/infra/ansible/roles/app/tasks/deploy.yml index 687e512..06db3c6 100644 --- a/infra/ansible/roles/app/tasks/deploy.yml +++ b/infra/ansible/roles/app/tasks/deploy.yml @@ -1,9 +1,7 @@ --- - name: Deploy each environment (sequentially to save RAM) - ansible.builtin.shell: | - {{ scripts_dir }}/deploy.sh {{ item.name }} {{ item.port }} + ansible.builtin.include_tasks: deploy_env.yml loop: "{{ app_environments }}" loop_control: - label: "{{ item.name }}" - register: deploy_result - changed_when: true + loop_var: env + label: "{{ env.name }}" diff --git a/infra/ansible/roles/app/tasks/deploy_env.yml b/infra/ansible/roles/app/tasks/deploy_env.yml new file mode 100644 index 0000000..795d568 --- /dev/null +++ b/infra/ansible/roles/app/tasks/deploy_env.yml @@ -0,0 +1,98 @@ +--- +- name: "{{ env.name }} | Build app image" + ansible.builtin.command: + argv: + - docker + - build + - --build-arg + - "NEXT_PUBLIC_SERVER_URL=https://{{ env.domain }}" + - --build-arg + - "NEXT_PUBLIC_SITE_ID={{ env.site_id }}" + - -t + - "church-website:{{ env.name }}" + - "{{ repo_dir }}" + changed_when: true + +- name: "{{ env.name }} | Build migration image" + ansible.builtin.command: + argv: + - docker + - build + - --target + - builder + - --build-arg + - "NEXT_PUBLIC_SERVER_URL=https://{{ env.domain }}" + - --build-arg + - "NEXT_PUBLIC_SITE_ID={{ env.site_id }}" + - -t + - "church-website-migrate:{{ env.name }}" + - "{{ repo_dir }}" + changed_when: true + +- name: "{{ env.name }} | Run database migrations" + ansible.builtin.command: + argv: + - docker + - run + - --rm + - --network + - "{{ docker_network }}" + - --env-file + - "{{ envs_dir }}/{{ env.name }}/.env" + - "church-website-migrate:{{ env.name }}" + - npx + - payload + - migrate + changed_when: true + +- name: "{{ env.name }} | Stop old container" + ansible.builtin.command: "docker stop app-{{ env.name }}" + failed_when: false + changed_when: true + +- name: "{{ env.name }} | Remove old container" + ansible.builtin.command: "docker rm app-{{ env.name }}" + failed_when: false + changed_when: true + +- name: "{{ env.name }} | Start new container" + ansible.builtin.command: + argv: + - docker + - run + - -d + - --name + - "app-{{ env.name }}" + - --restart + - unless-stopped + - --network + - "{{ docker_network }}" + - --env-file + - "{{ envs_dir }}/{{ env.name }}/.env" + - -v + - "uploads-{{ env.name }}-media:/app/media" + - -v + - "uploads-{{ env.name }}-documents:/app/documents" + - -p + - "127.0.0.1:{{ env.port }}:3000" + - "church-website:{{ env.name }}" + changed_when: true + +- name: "{{ env.name }} | Fix upload volume permissions" + ansible.builtin.command: + argv: + - docker + - exec + - -u + - "0" + - "app-{{ env.name }}" + - chown + - -R + - 1001:1001 + - /app/media + - /app/documents + changed_when: true + +- name: "{{ env.name }} | Prune old Docker images" + ansible.builtin.command: docker image prune -f + changed_when: true