diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..b158ff0 --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,60 @@ +name: Build and Push Docker Image + +on: + push: + branches: + - main + +env: + REGISTRY: git.cbraaten.dev + IMAGE_NAME: git.cbraaten.dev/caleb/blog + +jobs: + build: + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Get short SHA + id: sha + run: echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + + - name: Print Secrets + run: | + if [ -z "${{ secrets.REGISTRY_USERNAME }}" ]; then + echo "ERROR: REGISTRY_USERNAME secret is not set" + exit 1 + else + echo "✓ REGISTRY_USERNAME is set" + fi + if [ -z "${{ secrets.REGISTRY_PASSWORD }}" ]; then + echo "ERROR: REGISTRY_PASSWORD secret is not set" + exit 1 + else + echo "✓ REGISTRY_PASSWORD is set" + fi + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Build and Push Docker Image + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile + push: true + tags: | + ${{ env.IMAGE_NAME }}:latest + ${{ env.IMAGE_NAME }}:${{ steps.sha.outputs.short_sha }} diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..32f0318 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,46 @@ +name: Deploy to Nomad + +on: + workflow_run: + workflows: ["Build and Push Docker Image"] + types: + - completed + +env: + NOMAD_ADDR: ${{ secrets.NOMAD_ADDR }} + NOMAD_TOKEN: ${{ secrets.NOMAD_TOKEN }} + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Get image tag from git SHA + id: tag + run: | + image_tag=$(git rev-parse --short HEAD) + echo "image_tag=$image_tag" >> $GITHUB_OUTPUT + + - name: Update image tag in job definition + run: | + # Replace the image tag placeholder with actual tag + jq '.Job.TaskGroups[0].Tasks[0].Config.image |= sub(":.*"; ":${{ steps.tag.outputs.image_tag }}")' \ + blog.nomad.json > blog.nomad.final.json + + - name: Submit job via Nomad API + run: | + curl -s -X POST \ + -H "X-Nomad-Token: ${{ env.NOMAD_TOKEN }}" \ + -H "Content-Type: application/json" \ + -d @blog.nomad.final.json \ + "${{ env.NOMAD_ADDR }}/v1/jobs" + + - name: Verify deployment + run: | + echo "Waiting for deployment to stabilize..." + sleep 5 + curl -X GET \ + -H "X-Nomad-Token: ${{ env.NOMAD_TOKEN }}" \ + "${{ env.NOMAD_ADDR }}/v1/job/blog" diff --git a/blog.nomad.hcl b/blog.nomad.hcl new file mode 100644 index 0000000..522212f --- /dev/null +++ b/blog.nomad.hcl @@ -0,0 +1,54 @@ +# export to json for ci/cd scripts with the following command +# nomad job run -output blog.nomad.hcl > blog.nomad.json + +locals { + HOST = "cbraaten.dev" +} + +variable "image_tag" { + type = string + default = "latest" +} + +job "blog" { + type = "service" + + group "blog" { + count = 1 + + network { + port "http" { + to = 3000 + } + } + + service { + name = "blog" + provider = "consul" + port = "http" + + tags = [ + "traefik.enable=true", + "traefik.http.routers.domainredirect.tls=true", + "traefik.http.routers.domainredirect.entrypoints=websecure", + "traefik.http.routers.domainredirect.rule=Host(`${local.HOST}`)", + ] + } + + task "blog" { + driver = "docker" + + config { + image = "git.cbraaten.dev/caleb/blog:${var.image_tag}" + ports = ["http"] + } + } + + update { + max_parallel = 1 + min_healthy_time = "10s" + healthy_deadline = "3m" + auto_revert = true + } + } +} diff --git a/blog.nomad.json b/blog.nomad.json new file mode 100644 index 0000000..e3d69ad --- /dev/null +++ b/blog.nomad.json @@ -0,0 +1,164 @@ +{ + "Job": { + "Region": null, + "Namespace": null, + "ID": "blog", + "Name": "blog", + "Type": "service", + "Priority": null, + "AllAtOnce": null, + "Datacenters": null, + "NodePool": null, + "Constraints": null, + "Affinities": null, + "TaskGroups": [ + { + "Name": "blog", + "Count": 1, + "Constraints": null, + "Affinities": null, + "Tasks": [ + { + "Name": "blog", + "Driver": "docker", + "User": "", + "Lifecycle": null, + "Config": { + "image": "git.cbraaten.dev/caleb/blog:latest", + "ports": [ + "http" + ] + }, + "Constraints": null, + "Affinities": null, + "Env": null, + "Services": null, + "Resources": null, + "RestartPolicy": null, + "Meta": null, + "KillTimeout": null, + "LogConfig": null, + "Artifacts": null, + "Vault": null, + "Consul": null, + "Templates": null, + "DispatchPayload": null, + "VolumeMounts": null, + "Leader": false, + "ShutdownDelay": 0, + "KillSignal": "", + "Kind": "", + "ScalingPolicies": null, + "Secrets": null, + "Identity": null, + "Identities": null, + "Actions": null, + "Schedule": null + } + ], + "Spreads": null, + "Volumes": null, + "RestartPolicy": null, + "Disconnect": null, + "ReschedulePolicy": null, + "EphemeralDisk": null, + "Update": { + "Stagger": null, + "MaxParallel": 1, + "HealthCheck": null, + "MinHealthyTime": 10000000000, + "HealthyDeadline": 180000000000, + "ProgressDeadline": null, + "Canary": null, + "AutoRevert": true, + "AutoPromote": null + }, + "Migrate": null, + "Networks": [ + { + "Mode": "", + "Device": "", + "CIDR": "", + "IP": "", + "DNS": null, + "ReservedPorts": null, + "DynamicPorts": [ + { + "Label": "http", + "Value": 0, + "To": 3000, + "HostNetwork": "", + "IgnoreCollision": false + } + ], + "Hostname": "", + "MBits": null, + "CNI": null + } + ], + "Meta": null, + "Services": [ + { + "Name": "blog", + "Tags": [ + "traefik.enable=true", + "traefik.http.routers.domainredirect.tls=true", + "traefik.http.routers.domainredirect.entrypoints=websecure", + "traefik.http.routers.domainredirect.rule=Host(`cbraaten.dev`)" + ], + "CanaryTags": null, + "EnableTagOverride": false, + "PortLabel": "http", + "AddressMode": "", + "Address": "", + "Checks": null, + "CheckRestart": null, + "Connect": null, + "Meta": null, + "CanaryMeta": null, + "TaggedAddresses": null, + "TaskName": "", + "OnUpdate": "", + "Identity": null, + "Weights": null, + "Provider": "consul", + "Cluster": "", + "Kind": "" + } + ], + "ShutdownDelay": null, + "StopAfterClientDisconnect": null, + "MaxClientDisconnect": null, + "Scaling": null, + "Consul": null, + "PreventRescheduleOnLost": null + } + ], + "Update": null, + "Multiregion": null, + "Spreads": null, + "Periodic": null, + "ParameterizedJob": null, + "Reschedule": null, + "Migrate": null, + "Meta": null, + "UI": null, + "Stop": null, + "ParentID": null, + "Dispatched": false, + "DispatchIdempotencyToken": null, + "Payload": null, + "ConsulNamespace": null, + "VaultNamespace": null, + "NomadTokenID": null, + "Status": null, + "StatusDescription": null, + "Stable": null, + "Version": null, + "SubmitTime": null, + "CreateIndex": null, + "ModifyIndex": null, + "JobModifyIndex": null, + "VersionTag": null + } +}