--- - name: Build and Push Initial Docker Image hosts: production become: no gather_facts: yes vars: vault_file: "{{ playbook_dir }}/../secrets/production.vault.yml" build_repo_path: "/home/deploy/michaelschiemer" build_repo_url: "{{ git_repository_url_default | default('https://git.michaelschiemer.de/michael/michaelschiemer.git') }}" build_repo_branch_default: "main" # Local repository path for cloning (temporary) local_repo_path: "/tmp/michaelschiemer-build-{{ ansible_date_time.epoch }}" # Check if local repository exists (project root) local_repo_check_path: "{{ playbook_dir | default('') | dirname | dirname | dirname }}" image_name: "{{ app_name | default('framework') }}" image_tag_default: "latest" registry_url: "{{ docker_registry | default('localhost:5000') }}" registry_username: "{{ vault_docker_registry_username | default(docker_registry_username_default | default('admin')) }}" registry_password: "{{ vault_docker_registry_password | default('') }}" pre_tasks: - name: Verify vault file exists ansible.builtin.stat: path: "{{ vault_file }}" register: vault_stat delegate_to: localhost become: no - name: Load vault secrets ansible.builtin.include_vars: file: "{{ vault_file }}" when: vault_stat.stat.exists no_log: yes ignore_errors: yes delegate_to: localhost become: no - name: Verify registry password is set ansible.builtin.fail: msg: | Registry password is required! Please set vault_docker_registry_password in: {{ vault_file }} Or pass it via extra vars: -e "registry_password=your-password" when: registry_password | string | trim == '' tasks: - name: Set build variables ansible.builtin.set_fact: build_repo_branch: "{{ build_repo_branch | default(build_repo_branch_default) }}" image_tag: "{{ build_image_tag | default(image_tag_default) }}" - name: Display build information ansible.builtin.debug: msg: | Building Docker Image: - Repository: {{ build_repo_url }} - Branch: {{ build_repo_branch }} - Build Path: {{ build_repo_path }} - Registry: {{ registry_url }} - Image: {{ image_name }}:{{ image_tag }} - Username: {{ registry_username }} - name: Check if local repository exists (project root) ansible.builtin.stat: path: "{{ local_repo_check_path }}/.git" delegate_to: localhost register: local_repo_exists become: no - name: Configure Git to skip SSL verification for git.michaelschiemer.de (local) ansible.builtin.command: | git config --global http.https://git.michaelschiemer.de.sslVerify false delegate_to: localhost changed_when: false failed_when: false become: no when: not local_repo_exists.stat.exists - name: Determine Git URL with authentication if token is available ansible.builtin.set_fact: git_repo_url_with_auth: >- {%- if vault_git_token is defined and vault_git_token | string | trim != '' -%} https://{{ vault_git_token }}@git.michaelschiemer.de/michael/michaelschiemer.git {%- elif vault_git_username is defined and vault_git_username | string | trim != '' and vault_git_password is defined and vault_git_password | string | trim != '' -%} https://{{ vault_git_username }}:{{ vault_git_password }}@git.michaelschiemer.de/michael/michaelschiemer.git {%- else -%} {{ build_repo_url }} {%- endif -%} no_log: yes - name: Debug Git URL (without credentials) ansible.builtin.debug: msg: | Git Repository Configuration: - Original URL: {{ build_repo_url }} - Local repo exists: {{ 'YES' if local_repo_exists.stat.exists else 'NO' }} - Local repo path: {{ local_repo_check_path }} - Using authentication: {{ 'YES' if (vault_git_token is defined and vault_git_token | string | trim != '') or (vault_git_username is defined and vault_git_username | string | trim != '') else 'NO' }} - Auth method: {{ 'Token' if (vault_git_token is defined and vault_git_token | string | trim != '') else 'Username/Password' if (vault_git_username is defined and vault_git_username | string | trim != '') else 'None' }} no_log: yes - name: Use existing local repository or clone to temporary location block: - name: Clone repository to temporary location (local) ansible.builtin.git: repo: "{{ git_repo_url_with_auth }}" dest: "{{ local_repo_path }}" version: "{{ build_repo_branch }}" force: yes update: yes delegate_to: localhost register: git_result changed_when: git_result.changed environment: GIT_SSL_NO_VERIFY: "1" no_log: yes when: not local_repo_exists.stat.exists - name: Set local repository path to existing project root ansible.builtin.set_fact: source_repo_path: "{{ local_repo_check_path }}" when: local_repo_exists.stat.exists - name: Set local repository path to cloned temporary location ansible.builtin.set_fact: source_repo_path: "{{ local_repo_path }}" when: not local_repo_exists.stat.exists - name: Display repository source ansible.builtin.debug: msg: | Repository source: - Using existing local repo: {{ 'YES' if local_repo_exists.stat.exists else 'NO' }} - Source path: {{ source_repo_path }} - Branch: {{ build_repo_branch }} - name: Ensure build directory exists on server ansible.builtin.file: path: "{{ build_repo_path }}" state: directory mode: '0755' owner: "{{ ansible_user }}" group: "{{ ansible_user }}" - name: Copy repository to server (excluding .git and build artifacts) ansible.builtin.synchronize: src: "{{ source_repo_path }}/" dest: "{{ build_repo_path }}/" delete: no recursive: yes rsync_opts: - "--chmod=D755,F644" - "--exclude=.git" - "--exclude=.gitignore" - "--exclude=node_modules" - "--exclude=vendor" - "--exclude=.env" - "--exclude=.env.*" - "--exclude=*.log" - "--exclude=.idea" - "--exclude=.vscode" - "--exclude=.DS_Store" - "--exclude=*.swp" - "--exclude=*.swo" - "--exclude=*~" - "--exclude=.phpunit.result.cache" - "--exclude=coverage" - "--exclude=.phpunit.cache" - "--exclude=public/assets" - "--exclude=storage/logs" - "--exclude=storage/framework/cache" - "--exclude=storage/framework/sessions" - "--exclude=storage/framework/views" - name: Clean up temporary cloned repository ansible.builtin.file: path: "{{ local_repo_path }}" state: absent delegate_to: localhost become: no when: - not local_repo_exists.stat.exists - local_repo_path is defined - name: Check if Dockerfile.production exists on server ansible.builtin.stat: path: "{{ build_repo_path }}/Dockerfile.production" register: dockerfile_stat - name: Fail if Dockerfile.production not found ansible.builtin.fail: msg: | Dockerfile.production not found at {{ build_repo_path }}/Dockerfile.production Please verify: 1. The repository was copied successfully to {{ build_repo_path }} 2. The Dockerfile.production file exists in the repository 3. The source repository path is correct: {{ source_repo_path }} when: not dockerfile_stat.stat.exists - name: Check if entrypoint script exists on server ansible.builtin.stat: path: "{{ build_repo_path }}/docker/entrypoint.sh" register: entrypoint_stat - name: Display entrypoint script status ansible.builtin.debug: msg: | Entrypoint Script Check: - Path: {{ build_repo_path }}/docker/entrypoint.sh - Exists: {{ entrypoint_stat.stat.exists | default(false) }} {% if entrypoint_stat.stat.exists %} - Mode: {{ entrypoint_stat.stat.mode | default('unknown') }} - Size: {{ entrypoint_stat.stat.size | default(0) }} bytes {% endif %} when: not ansible_check_mode - name: Fail if entrypoint script not found ansible.builtin.fail: msg: | Entrypoint script not found at {{ build_repo_path }}/docker/entrypoint.sh This file is required for the Docker image build! Please verify: 1. The file exists in the source repository: {{ source_repo_path }}/docker/entrypoint.sh 2. The rsync operation copied the file successfully when: not entrypoint_stat.stat.exists - name: Convert entrypoint script to LF line endings ansible.builtin.shell: | sed -i 's/\r$//' "{{ build_repo_path }}/docker/entrypoint.sh" when: entrypoint_stat.stat.exists changed_when: false failed_when: false - name: Verify entrypoint script has LF line endings ansible.builtin.shell: | if head -1 "{{ build_repo_path }}/docker/entrypoint.sh" | od -c | grep -q "\\r"; then echo "CRLF_DETECTED" else echo "LF_ONLY" fi register: line_ending_check changed_when: false when: entrypoint_stat.stat.exists - name: Display line ending check result ansible.builtin.debug: msg: | Entrypoint Script Line Endings: - Status: {{ line_ending_check.stdout | default('unknown') }} {% if 'CRLF_DETECTED' in line_ending_check.stdout %} ⚠️ WARNING: Script still has CRLF line endings after conversion attempt! {% else %} ✅ Script has LF line endings {% endif %} when: - entrypoint_stat.stat.exists - not ansible_check_mode - name: Login to Docker registry community.docker.docker_login: registry_url: "{{ registry_url }}" username: "{{ registry_username }}" password: "{{ registry_password }}" no_log: yes register: login_result - name: Verify registry login ansible.builtin.debug: msg: "✅ Successfully logged in to {{ registry_url }}" when: not login_result.failed | default(false) - name: Fail if registry login failed ansible.builtin.fail: msg: | Failed to login to Docker registry! Registry: {{ registry_url }} Username: {{ registry_username }} Please verify: 1. The registry is running and accessible 2. The username and password are correct 3. The registry URL is correct when: login_result.failed | default(false) - name: Verify Docker Buildx is available ansible.builtin.command: docker buildx version register: buildx_check changed_when: false failed_when: false - name: Warn if Buildx is not available ansible.builtin.debug: msg: | ⚠️ Docker Buildx not found. BuildKit features may not work. Install with: apt-get install docker-buildx-plugin when: buildx_check.rc != 0 - name: Set build cache option ansible.builtin.set_fact: build_no_cache: "{{ build_no_cache | default('false') | bool }}" - name: Build and push Docker image with BuildKit ansible.builtin.shell: | set -e BUILD_ARGS="" {% if build_no_cache | bool %} BUILD_ARGS="--no-cache" {% endif %} DOCKER_BUILDKIT=1 docker buildx build \ --platform linux/amd64 \ --file {{ build_repo_path }}/Dockerfile.production \ --tag {{ registry_url }}/{{ image_name }}:{{ image_tag }} \ --push \ --progress=plain \ $BUILD_ARGS \ {{ build_repo_path }} register: build_result environment: DOCKER_BUILDKIT: "1" changed_when: build_result.rc == 0 failed_when: build_result.rc != 0 async: 3600 poll: 10 - name: Display build result ansible.builtin.debug: msg: | Build result: - Image: {{ registry_url }}/{{ image_name }}:{{ image_tag }} - Return code: {{ build_result.rc | default('unknown') }} - Changed: {{ build_result.changed | default(false) }} - Failed: {{ build_result.failed | default(false) }} when: build_result is defined - name: Display build output on failure ansible.builtin.debug: msg: | Build failed! Output: {{ build_result.stdout_lines | default([]) | join('\n') }} Error output: {{ build_result.stderr_lines | default([]) | join('\n') }} when: - build_result is defined - build_result.rc | default(0) != 0 - name: Verify image exists locally ansible.builtin.command: | docker image inspect {{ registry_url }}/{{ image_name }}:{{ image_tag }} register: image_check changed_when: false failed_when: false - name: Verify entrypoint script in built image shell: | CONTAINER_ID=$(docker create {{ registry_url }}/{{ image_name }}:{{ image_tag }} 2>/dev/null) && \ if docker cp $CONTAINER_ID:/usr/local/bin/entrypoint.sh /tmp/entrypoint_verify.sh 2>&1; then \ echo "FILE_EXISTS"; \ ls -la /tmp/entrypoint_verify.sh; \ head -3 /tmp/entrypoint_verify.sh; \ file /tmp/entrypoint_verify.sh; \ rm -f /tmp/entrypoint_verify.sh; \ else \ echo "FILE_NOT_FOUND"; \ fi && \ docker rm $CONTAINER_ID >/dev/null 2>&1 || true register: image_entrypoint_check changed_when: false failed_when: false - name: Display entrypoint verification result debug: msg: | ========================================== Entrypoint Script Verification in Built Image ========================================== Image: {{ registry_url }}/{{ image_name }}:{{ image_tag }} Verification Result: {{ image_entrypoint_check.stdout | default('Check failed') }} {% if 'FILE_NOT_FOUND' in image_entrypoint_check.stdout %} ⚠️ CRITICAL: Entrypoint script NOT FOUND in built image! This means the Docker build did not copy the entrypoint script. Possible causes: 1. The COPY command in Dockerfile.production failed silently 2. The docker/entrypoint.sh file was not in the build context 3. There was an issue with the multi-stage build Please check: 1. Build logs above for COPY errors 2. Verify docker/entrypoint.sh exists: ls -la {{ build_repo_path }}/docker/entrypoint.sh 3. Rebuild with verbose output to see COPY step {% elif 'FILE_EXISTS' in image_entrypoint_check.stdout %} ✅ Entrypoint script found in built image {% endif %} ========================================== - name: Display image information ansible.builtin.debug: msg: | ✅ Image built and pushed successfully! Registry: {{ registry_url }} Image: {{ image_name }}:{{ image_tag }} Local: {{ 'Available' if image_check.rc == 0 else 'Not found locally' }} Next steps: 1. Run setup-infrastructure.yml to deploy the application stack 2. Or manually deploy using docker-compose when: image_check.rc == 0 - name: Warn if image not found locally ansible.builtin.debug: msg: | ⚠️ Image was pushed but not found locally. This is normal if the image was pushed to a remote registry. Verify the image exists in the registry: curl -u {{ registry_username }}:{{ registry_password }} http://{{ registry_url }}/v2/{{ image_name }}/tags/list when: image_check.rc != 0