Deploy from a Gitea registry image instead of building on the server
CI now builds and pushes the image to the Gitea container registry, links the package, and the deploy job ships only deploy/docker-compose.yml and an .env naming the image to /opt/vending-machine-game. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
+60
-11
@@ -1,9 +1,13 @@
|
|||||||
# Gitea Actions. While .gitea/workflows exists Gitea ignores .github/workflows (GitHub Pages only).
|
# Gitea Actions. While .gitea/workflows exists Gitea ignores .github/workflows (GitHub Pages only).
|
||||||
#
|
#
|
||||||
# Every push and pull request: install, test and build.
|
# Every push and pull request: install, test and build.
|
||||||
# Push to main: then deploy to the server over SSH and start it with docker compose.
|
# Push to main: build the Docker image, push it to the Gitea registry, then have the server pull it.
|
||||||
|
# The server holds no source code, only deploy/docker-compose.yml and a .env naming the image.
|
||||||
#
|
#
|
||||||
# Repository settings → Actions → Secrets:
|
# Repository settings → Actions → Secrets:
|
||||||
|
# REGISTRY_USERNAME Gitea user that publishes images
|
||||||
|
# REGISTRY_TOKEN its personal access token with package Read and Write
|
||||||
|
# (the built-in GITEA_TOKEN cannot publish packages)
|
||||||
# DEPLOY_HOST server address (deploy is skipped while this is empty)
|
# DEPLOY_HOST server address (deploy is skipped while this is empty)
|
||||||
# DEPLOY_SSH_KEY private key whose public key is in the server user's ~/.ssh/authorized_keys
|
# DEPLOY_SSH_KEY private key whose public key is in the server user's ~/.ssh/authorized_keys
|
||||||
# DEPLOY_USER SSH user, default root; it must be able to run docker
|
# DEPLOY_USER SSH user, default root; it must be able to run docker
|
||||||
@@ -12,8 +16,7 @@
|
|||||||
# Repository settings → Actions → Variables (optional):
|
# Repository settings → Actions → Variables (optional):
|
||||||
# NPM_REGISTRY npm mirror used by the runner and the server's Docker build when
|
# NPM_REGISTRY npm mirror used by the runner and the server's Docker build when
|
||||||
# registry.npmjs.org is unreachable, e.g. from servers in Iran
|
# registry.npmjs.org is unreachable, e.g. from servers in Iran
|
||||||
# DEPLOY_PATH directory on the server, default vending-machine-game (in the user's home)
|
# DEPLOY_PATH directory on the server, default /opt/vending-machine-game
|
||||||
# SITE_ADDRESS domains served with HTTPS, default "robotmarkets.ir, www.robotmarkets.ir"
|
|
||||||
# HEALTH_URL URL checked after deploy, e.g. https://robotmarkets.ir/
|
# HEALTH_URL URL checked after deploy, e.g. https://robotmarkets.ir/
|
||||||
name: CI/CD
|
name: CI/CD
|
||||||
|
|
||||||
@@ -49,11 +52,56 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
|
||||||
deploy:
|
publish:
|
||||||
needs: test
|
needs: test
|
||||||
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
|
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
|
outputs:
|
||||||
|
image: ${{ steps.push.outputs.image }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Build, push and link the image
|
||||||
|
id: push
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
||||||
|
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||||
|
SOURCE_REPOSITORY: ${{ gitea.repository }}
|
||||||
|
COMMIT_SHA: ${{ gitea.sha }}
|
||||||
|
RUN_ID: ${{ gitea.run_id }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
REGISTRY='git.my-rm.com'
|
||||||
|
OWNER="${SOURCE_REPOSITORY%%/*}"
|
||||||
|
REPO="${SOURCE_REPOSITORY#*/}"
|
||||||
|
IMAGE="$REGISTRY/${OWNER,,}/${REPO,,}:build-$(date -u +%Y%m%d-%H%M%S)-${RUN_ID}-${COMMIT_SHA:0:7}"
|
||||||
|
|
||||||
|
# Keep this job's registry credentials away from the host's own logins.
|
||||||
|
DOCKER_CONFIG="$(mktemp -d)"
|
||||||
|
export DOCKER_CONFIG
|
||||||
|
trap 'rm -rf "$DOCKER_CONFIG"' EXIT
|
||||||
|
|
||||||
|
printf '%s' "$REGISTRY_TOKEN" | docker login "$REGISTRY" --username "$REGISTRY_USERNAME" --password-stdin
|
||||||
|
docker build \
|
||||||
|
--build-arg "NPM_REGISTRY=$NPM_CONFIG_REGISTRY" \
|
||||||
|
--label "org.opencontainers.image.source=https://$REGISTRY/$SOURCE_REPOSITORY" \
|
||||||
|
--label "org.opencontainers.image.revision=$COMMIT_SHA" \
|
||||||
|
--tag "$IMAGE" .
|
||||||
|
docker push "$IMAGE"
|
||||||
|
|
||||||
|
# Show the package under this repository's Packages tab.
|
||||||
|
curl --fail-with-body --silent --show-error --request POST \
|
||||||
|
--header "Authorization: token $REGISTRY_TOKEN" \
|
||||||
|
"https://$REGISTRY/api/v1/packages/$OWNER/container/${REPO,,}/-/link/$REPO"
|
||||||
|
|
||||||
|
echo "image=$IMAGE" >> "$GITEA_OUTPUT"
|
||||||
|
echo "Published and linked: $IMAGE"
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
needs: publish
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
container:
|
container:
|
||||||
image: node:22-bookworm
|
image: node:22-bookworm
|
||||||
env:
|
env:
|
||||||
@@ -63,8 +111,8 @@ jobs:
|
|||||||
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||||
DEPLOY_KNOWN_HOSTS: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
|
DEPLOY_KNOWN_HOSTS: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
|
||||||
DEPLOY_PATH: ${{ vars.DEPLOY_PATH }}
|
DEPLOY_PATH: ${{ vars.DEPLOY_PATH }}
|
||||||
SITE_ADDRESS: ${{ vars.SITE_ADDRESS }}
|
|
||||||
HEALTH_URL: ${{ vars.HEALTH_URL }}
|
HEALTH_URL: ${{ vars.HEALTH_URL }}
|
||||||
|
IMAGE: ${{ needs.publish.outputs.image }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Deploy to the server
|
- name: Deploy to the server
|
||||||
@@ -75,8 +123,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
port="${DEPLOY_PORT:-22}"
|
port="${DEPLOY_PORT:-22}"
|
||||||
target="${DEPLOY_USER:-root}@${DEPLOY_HOST}"
|
target="${DEPLOY_USER:-root}@${DEPLOY_HOST}"
|
||||||
dir="${DEPLOY_PATH:-vending-machine-game}"
|
dir="${DEPLOY_PATH:-/opt/vending-machine-game}"
|
||||||
site="${SITE_ADDRESS:-robotmarkets.ir, www.robotmarkets.ir}"
|
|
||||||
mkdir -p ~/.ssh && chmod 700 ~/.ssh
|
mkdir -p ~/.ssh && chmod 700 ~/.ssh
|
||||||
printf '%s\n' "$DEPLOY_SSH_KEY" > ~/.ssh/deploy_key && chmod 600 ~/.ssh/deploy_key
|
printf '%s\n' "$DEPLOY_SSH_KEY" > ~/.ssh/deploy_key && chmod 600 ~/.ssh/deploy_key
|
||||||
if [ -n "$DEPLOY_KNOWN_HOSTS" ]; then
|
if [ -n "$DEPLOY_KNOWN_HOSTS" ]; then
|
||||||
@@ -84,10 +131,12 @@ jobs:
|
|||||||
else
|
else
|
||||||
ssh-keyscan -p "$port" "$DEPLOY_HOST" > ~/.ssh/known_hosts 2>/dev/null
|
ssh-keyscan -p "$port" "$DEPLOY_HOST" > ~/.ssh/known_hosts 2>/dev/null
|
||||||
fi
|
fi
|
||||||
# Ship the checked-out sources (no git history) and rebuild/restart the container there.
|
# Only the compose file goes to the server; the app itself comes from the registry image.
|
||||||
tar czf - --exclude=./.git --exclude=./node_modules --exclude=./dist . \
|
# The server user must already be logged in to git.my-rm.com (docker login, package Read token).
|
||||||
| ssh -i ~/.ssh/deploy_key -p "$port" "$target" \
|
ssh_opts="-i $HOME/.ssh/deploy_key -p $port"
|
||||||
"mkdir -p '$dir' && tar xzf - -C '$dir' && cd '$dir' && SITE_ADDRESS='$site' NPM_REGISTRY='$NPM_CONFIG_REGISTRY' docker compose up -d --build --remove-orphans && docker image prune -f"
|
ssh $ssh_opts "$target" "mkdir -p '$dir'"
|
||||||
|
scp -i ~/.ssh/deploy_key -P "$port" deploy/docker-compose.yml "$target:$dir/docker-compose.yml"
|
||||||
|
ssh $ssh_opts "$target" "cd '$dir' && cp -f .env .env.previous 2>/dev/null; printf 'IMAGE=%s\n' '$IMAGE' > .env && docker compose pull && docker compose up -d --remove-orphans && docker image prune -f"
|
||||||
|
|
||||||
- name: Check the site
|
- name: Check the site
|
||||||
if: env.HEALTH_URL != '' && env.DEPLOY_HOST != ''
|
if: env.HEALTH_URL != '' && env.DEPLOY_HOST != ''
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Server deployment: runs the prebuilt image from the Gitea registry behind Nginx Proxy Manager.
|
||||||
|
# NPM terminates HTTPS and forwards to http://vending-machine-game:80 on the frontend_proxy network.
|
||||||
|
# IMAGE is set in .env next to this file (CI writes it on every deploy).
|
||||||
|
services:
|
||||||
|
game:
|
||||||
|
image: ${IMAGE:?set IMAGE in .env}
|
||||||
|
container_name: vending-machine-game
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
# Plain HTTP inside the network; no host ports are published.
|
||||||
|
SITE_ADDRESS: ":80"
|
||||||
|
networks:
|
||||||
|
- frontend_proxy
|
||||||
|
|
||||||
|
networks:
|
||||||
|
frontend_proxy:
|
||||||
|
external: true
|
||||||
Reference in New Issue
Block a user