22# DOCKER_USER, DOCKER_TOKEN - Docker Hub login
33# DEPLOYMENT_KEY - SSH private key for deploy host
44# DEPLOYMENT_USER - SSH user on deploy host (e.g. root)
5- # DEPLOYMENT_HOST, DEPLOYMENT_PORT - deploy server
5+ # DEPLOYMENT_HOST - deploy server hostname or IP only (no user@ prefix)
6+ # DEPLOYMENT_PORT - SSH port (e.g. 22)
67# SMTP_HOST, SMTP_PORT, SMTP_SECURE, SMTP_USER, SMTP_PASS, SMTP_FROM, SMTP_TO
7- # Optional vars (defaults in workflow): SMTP_FROM_NAME, NEXT_PUBLIC_SITE_URL
8+ # Optional: SMTP_FROM_NAME. Vars (defaults in workflow): NEXT_PUBLIC_SITE_URL
9+ # Note: ssh-keyscan uses DEPLOYMENT_HOST only; ssh/scp use DEPLOYMENT_USER@DEPLOYMENT_HOST.
10+ # Avoid single/double quotes and newlines in secret values (SMTP_PASS, DOCKER_TOKEN, etc.).
811name : Build, Push and Deploy
912
1013env :
1922 - develop
2023 workflow_dispatch :
2124
25+ concurrency :
26+ group : deploy-${{ github.ref }}
27+ cancel-in-progress : false
28+
2229jobs :
2330 build-and-push :
2431 runs-on : ubuntu-latest
@@ -34,16 +41,16 @@ jobs:
3441 - name : Configure variables
3542 id : conf
3643 run : |
37- echo "branch=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_OUTPUT
38- echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
44+ echo "branch=$(echo ${GITHUB_REF#refs/heads/})" >> " $GITHUB_OUTPUT"
45+ echo "sha_short=$(git rev-parse --short HEAD)" >> " $GITHUB_OUTPUT"
3946
4047 - name : Set job outputs
4148 id : set-outputs
4249 run : |
43- echo "sha_short=${{ steps.conf.outputs.sha_short }}" >> $GITHUB_OUTPUT
44- echo "branch=${{ steps.conf.outputs.branch }}" >> $GITHUB_OUTPUT
45- echo "image_tag=${{ steps.conf.outputs.branch }}-${{ steps.conf.outputs.sha_short }}" >> $GITHUB_OUTPUT
46- echo "full_image=${{ env.DOCKER_IMAGE }}:${{ steps.conf.outputs.branch }}-${{ steps.conf.outputs.sha_short }}" >> $GITHUB_OUTPUT
50+ echo "sha_short=${{ steps.conf.outputs.sha_short }}" >> " $GITHUB_OUTPUT"
51+ echo "branch=${{ steps.conf.outputs.branch }}" >> " $GITHUB_OUTPUT"
52+ echo "image_tag=${{ steps.conf.outputs.branch }}-${{ steps.conf.outputs.sha_short }}" >> " $GITHUB_OUTPUT"
53+ echo "full_image=${{ env.DOCKER_IMAGE }}:${{ steps.conf.outputs.branch }}-${{ steps.conf.outputs.sha_short }}" >> " $GITHUB_OUTPUT"
4754
4855 - name : Docker metadata
4956 id : meta
8693 runs-on : ubuntu-latest
8794 needs : build-and-push
8895 if : github.ref == 'refs/heads/main' && needs.build-and-push.result == 'success'
96+ env :
97+ FULL_IMAGE : ${{ needs.build-and-push.outputs.full_image }}
98+ IMAGE_TAG : ${{ needs.build-and-push.outputs.image_tag }}
99+ SHA_SHORT : ${{ needs.build-and-push.outputs.sha_short }}
89100 steps :
90101 - name : Checkout code
91102 uses : actions/checkout@v4
@@ -106,7 +117,7 @@ jobs:
106117
107118 - name : Generate docker-compose.yml from template
108119 run : |
109- sed -e "s|\${DOCKER_IMAGE}|${{ needs.build-and-push.outputs.full_image }} |g" \
120+ sed -e "s|\${DOCKER_IMAGE}|$FULL_IMAGE |g" \
110121 docker-compose.prod.yaml > /tmp/docker-compose.yml
111122
112123 - name : Copy docker-compose.yml to deployment host
@@ -145,19 +156,22 @@ jobs:
145156 "cd ${{ env.DEPLOYMENT_DIR }} && base64 -d .env.b64 > .env && chmod 600 .env && rm -f .env.b64"
146157
147158 - name : Login to Docker Hub on deployment host
159+ env :
160+ DOCKER_USER : ${{ secrets.DOCKER_USER }}
161+ DOCKER_TOKEN : ${{ secrets.DOCKER_TOKEN }}
148162 run : |
149- ssh -p ${{ secrets.DEPLOYMENT_PORT }} ${{ secrets.DEPLOYMENT_USER }}@${{ secrets.DEPLOYMENT_HOST }} \
150- "echo '${{ secrets.DOCKER_TOKEN }}' | docker login ${{ env.DOCKER_REGISTRY }} -u '${{ secrets. DOCKER_USER }}' --password-stdin"
163+ echo "$DOCKER_TOKEN" | ssh -p ${{ secrets.DEPLOYMENT_PORT }} ${{ secrets.DEPLOYMENT_USER }}@${{ secrets.DEPLOYMENT_HOST }} \
164+ "docker login ${{ env.DOCKER_REGISTRY }} -u $ DOCKER_USER --password-stdin"
151165
152166 - name : Check existing deployment
153167 id : check-existing
154168 run : |
155169 if ssh -p ${{ secrets.DEPLOYMENT_PORT }} ${{ secrets.DEPLOYMENT_USER }}@${{ secrets.DEPLOYMENT_HOST }} \
156170 "test -f ${{ env.DEPLOYMENT_DIR }}/docker-compose.yml"; then
157- echo "exists=true" >> $GITHUB_OUTPUT
171+ echo "exists=true" >> " $GITHUB_OUTPUT"
158172 echo "Existing docker-compose.yml found"
159173 else
160- echo "exists=false" >> $GITHUB_OUTPUT
174+ echo "exists=false" >> " $GITHUB_OUTPUT"
161175 echo "No existing docker-compose.yml found"
162176 fi
163177
@@ -175,8 +189,12 @@ jobs:
175189
176190 - name : Pull Docker image
177191 run : |
192+ if [ -z "$FULL_IMAGE" ]; then
193+ echo "::error::FULL_IMAGE is empty (build-and-push.outputs.full_image was not set)"
194+ exit 1
195+ fi
178196 ssh -p ${{ secrets.DEPLOYMENT_PORT }} ${{ secrets.DEPLOYMENT_USER }}@${{ secrets.DEPLOYMENT_HOST }} \
179- "docker pull ${{ needs.build-and-push.outputs.full_image }} "
197+ "docker pull $FULL_IMAGE "
180198
181199 - name : Deploy with docker compose
182200 run : |
@@ -192,7 +210,8 @@ jobs:
192210 run : |
193211 ssh -p ${{ secrets.DEPLOYMENT_PORT }} ${{ secrets.DEPLOYMENT_USER }}@${{ secrets.DEPLOYMENT_HOST }} \
194212 "cd ${{ env.DEPLOYMENT_DIR }} && \
195- if docker compose ps --format json | grep -q '\"State\":\"running\"'; then \
213+ running=\$(docker compose ps --format '{{ '{{' }}.State{{ '}}' }}' 2>/dev/null | grep -c running || true); \
214+ if [ \"\$running\" -gt 0 ]; then \
196215 echo 'Container is running'; \
197216 else \
198217 echo 'Container may not be running properly'; \
@@ -209,8 +228,8 @@ jobs:
209228 - name : Deployment info
210229 run : |
211230 echo "Successfully deployed:"
212- echo " Image: ${{ needs.build-and-push.outputs.full_image }} "
213- echo " Tag: ${{ needs.build-and-push.outputs.image_tag }} "
214- echo " SHA: ${{ needs.build-and-push.outputs.sha_short }} "
231+ echo " Image: $FULL_IMAGE "
232+ echo " Tag: $IMAGE_TAG "
233+ echo " SHA: $SHA_SHORT "
215234 echo " Host: ${{ secrets.DEPLOYMENT_USER }}@${{ secrets.DEPLOYMENT_HOST }}"
216235 echo " Directory: ${{ env.DEPLOYMENT_DIR }}"
0 commit comments