Skip to content

Commit

Permalink
Docker deployment via GitHub Actions (#115)
Browse files Browse the repository at this point in the history
* create prod compose

* add workflows

* add deploy workflow

* change port

* Format README, remove trailing backslash from example DEPLOYMENT_URL

* modify workflow

* fix image name

* yolo

* fix error

* remove needed file

* update

* fix compose file

* fix casing

* remove cache

* map to host

* fix redis

* remove arg

* fix deployment url

* fix sed

* fix url

* rename file

* generalize docker compose deploy

* use workflow_dispatch

* revert readme

---------

Co-authored-by: Faris Demirovic <farisdemirovic16@gmail.com>
  • Loading branch information
FelixTJDietrich and farisd16 authored Dec 6, 2024
1 parent 89fb800 commit 06fd713
Show file tree
Hide file tree
Showing 9 changed files with 289 additions and 7 deletions.
94 changes: 94 additions & 0 deletions .github/workflows/build-and-push-shared.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Move to ls1intum/.github/.github/workflows/build-and-push-docker-image.yml@main in the future
name: Build and Push Docker Image

on:
workflow_call:
inputs:
image-name:
type: string
default: ${{ github.repository }}
description: "The name for the docker image (Default: Repository name)"
docker-file:
type: string
default: Dockerfile
description: "The path to the Dockerfile (Default: ./Dockerfile)"
docker-context:
type: string
default: .
description: "The context for the Docker build (Default: .)"
build-args:
type: string
description: "List of additional build contexts (e.g., name=path)"
required: false
platforms:
type: string
description: "List of platforms for which to build the image"
default: linux/amd64,linux/arm64
registry:
type: string
default: ghcr.io
description: "The registry to push the image to (Default: ghcr.io)"

secrets:
registry-user:
required: false
registry-password:
required: false

outputs:
image-tag:
description: "The tag of the pushed image"
value: ${{ jobs.build.outputs.image-tag }}
jobs:
build:
name: Build Docker Image for ${{ inputs.image-name }}
runs-on: ubuntu-latest
outputs:
image-tag: ${{ steps.set-tag.outputs.image-tag }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1

- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: all

- name: Install Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ${{ inputs.registry }}
username: ${{ secrets.registry-user || github.actor }}
password: ${{ secrets.registry-password || secrets.GITHUB_TOKEN }}

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ inputs.registry }}/${{ inputs.image-name }}
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=sha,prefix=,format=long
- name: Set image tag output
id: set-tag
run: echo "::set-output name=image-tag::${{ steps.meta.outputs.version }}"

- name: Build and push Docker Image
uses: docker/build-push-action@v6
with:
context: ${{ inputs.docker-context }}
file: ${{ inputs.docker-file }}
platforms: ${{ inputs.platforms }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: ${{ inputs.build-args }}
push: true
16 changes: 16 additions & 0 deletions .github/workflows/build-and-push.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Build Docker Image

on:
pull_request:
push:
branches: [main]

jobs:
build-and-push-workflow:
name: Build and Push Docker Image
# TODO: uses: ls1intum/.github/.github/workflows/build-and-push-docker-image.yml@main
uses: ./.github/workflows/build-and-push-shared.yml
with:
image-name: ls1intum/apollon_standalone
docker-file: Dockerfile.redis
secrets: inherit
21 changes: 21 additions & 0 deletions .github/workflows/deploy-dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Deploy Dev

on:
workflow_dispatch:
inputs:
image-tag:
type: string
description: "The tag of the docker images to deploy"
required: true
jobs:
deploy:
# TODO: uses: ls1intum/.github/.github/workflows/deploy-docker-compose.yml@main
uses: ./.github/workflows/deploy-docker-compose-shared.yml
with:
environment: Dev
docker-compose-file: "./docker-compose.prod.yml"
image-tag: ${{ inputs.image-tag }}
env-vars: |
DEPLOYMENT_URL=${{ vars.DEPLOYMENT_URL }}
APOLLON_REDIS_DIAGRAM_TTL=${{ vars.APOLLON_REDIS_DIAGRAM_TTL }}
secrets: inherit
106 changes: 106 additions & 0 deletions .github/workflows/deploy-docker-compose-shared.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# ls1intum/.github/workflows/deploy-docker-compose.yml
name: Deploy Docker Compose

on:
workflow_call:
inputs:
environment:
type: string
description: "The deployment environment (e.g., production, staging)"
required: true
docker-compose-file:
type: string
default: "./docker-compose.yml"
description: "Path to the Docker Compose file (Default: ./docker-compose.yml)"
image-tag:
type: string
default: latest
description: "Tag of the Docker images to deploy (Default: latest)"
env-vars:
type: string
description: "Additional environment variables in KEY=VALUE format, separated by newlines"
required: false

jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: ${{ inputs.environment }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4

- name: SSH to VM and Execute Docker-Compose Down (if exists)
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ vars.VM_HOST }}
username: ${{ vars.VM_USERNAME }}
key: ${{ secrets.VM_SSH_PRIVATE_KEY }}
proxy_host: ${{ vars.DEPLOYMENT_GATEWAY_HOST }}
proxy_username: ${{ vars.DEPLOYMENT_GATEWAY_USER }}
proxy_key: ${{ secrets.DEPLOYMENT_GATEWAY_SSH_KEY }}
proxy_port: ${{ vars.DEPLOYMENT_GATEWAY_PORT }}
script: |
#!/bin/bash
set -e # Exit immediately if a command exits with a non-zero status
COMPOSE_FILE="${{ inputs.docker-compose-file }}"
# Check if docker-compose.prod.yml exists
if [ -f "$COMPOSE_FILE" ]; then
echo "$COMPOSE_FILE found."
# Check if .env exists
if [ -f ".env" ]; then
docker compose -f "$COMPOSE_FILE" --env-file=".env" down --remove-orphans --rmi all
else
docker compose -f "$COMPOSE_FILE" down --remove-orphans --rmi all
fi
else
echo "$COMPOSE_FILE does not exist. Skipping docker compose down."
fi
- name: Copy Docker Compose File to VM Host
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ vars.VM_HOST }}
username: ${{ vars.VM_USERNAME }}
key: ${{ secrets.VM_SSH_PRIVATE_KEY }}
proxy_host: ${{ vars.DEPLOYMENT_GATEWAY_HOST }}
proxy_username: ${{ vars.DEPLOYMENT_GATEWAY_USER }}
proxy_key: ${{ secrets.DEPLOYMENT_GATEWAY_SSH_KEY }}
proxy_port: ${{ vars.DEPLOYMENT_GATEWAY_PORT }}
source: ${{ inputs.docker-compose-file }}
target: /home/${{ vars.VM_USERNAME }}

- name: SSH to VM and Create .env File
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ vars.VM_HOST }}
username: ${{ vars.VM_USERNAME }}
key: ${{ secrets.VM_SSH_PRIVATE_KEY }}
proxy_host: ${{ vars.DEPLOYMENT_GATEWAY_HOST }}
proxy_username: ${{ vars.DEPLOYMENT_GATEWAY_USER }}
proxy_key: ${{ secrets.DEPLOYMENT_GATEWAY_SSH_KEY }}
proxy_port: ${{ vars.DEPLOYMENT_GATEWAY_PORT }}
script: |
touch .env
echo "ENVIRONMENT=${{ inputs.environment }}" > .env
echo "IMAGE_TAG=${{ inputs.image-tag }}" >> .env
if [ "${{ inputs.env-vars }}" != "" ]; then
echo "${{ inputs.env-vars }}" >> .env
fi
- name: SSH to VM and Execute Docker Compose Up
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ vars.VM_HOST }}
username: ${{ vars.VM_USERNAME }}
key: ${{ secrets.VM_SSH_PRIVATE_KEY }}
proxy_host: ${{ vars.DEPLOYMENT_GATEWAY_HOST }}
proxy_username: ${{ vars.DEPLOYMENT_GATEWAY_USER }}
proxy_key: ${{ secrets.DEPLOYMENT_GATEWAY_SSH_KEY }}
proxy_port: ${{ vars.DEPLOYMENT_GATEWAY_PORT }}
script: |
docker compose -f ${{ inputs.docker-compose-file }} --env-file=.env up --pull=always -d
8 changes: 3 additions & 5 deletions Dockerfile.redis
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@ RUN apk add --no-cache \
pango-dev \
giflib-dev


ARG DEPLOYMENT_URL="http://localhost:8080"

ENV APOLLON_REDIS_URL=""
ENV DEPLOYMENT_URL=${DEPLOYMENT_URL}
ENV DEPLOYMENT_URL="http://localhost:8080"
ENV APOLLON_REDIS_URL="redis://localhost:6379"
ENV APOLLON_REDIS_DIAGRAM_TTL="30d"

WORKDIR /app

Expand Down
36 changes: 36 additions & 0 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
version: '3.8'

services:
redis:
image: redis/redis-stack-server:7.4.0-v1
container_name: apollon-redis
volumes:
- ./redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- apollon-network

apollon-standalone:
image: "ghcr.io/ls1intum/apollon_standalone:${IMAGE_TAG}"
container_name: apollon-standalone
environment:
- APOLLON_REDIS_URL=redis://apollon-redis:6379
- APOLLON_REDIS_DIAGRAM_TTL=${APOLLON_REDIS_DIAGRAM_TTL}
- DEPLOYMENT_URL=${DEPLOYMENT_URL}
restart: unless-stopped
ports:
- "8080:8080"
depends_on:
redis:
condition: service_healthy
networks:
- apollon-network

networks:
apollon-network:
driver: bridge
11 changes: 11 additions & 0 deletions packages/server/src/main/server.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import fs from 'fs';
import path from 'path';
import bodyParser from 'body-parser';
import express, { RequestHandler } from 'express';
import * as Sentry from '@sentry/node';
Expand All @@ -19,6 +21,15 @@ if (process.env.SENTRY_DSN) {
Sentry.setTag('package', 'server');
}

// Replace http://localhost:8080 with the actual process.env.DEPLOYMENT_URL
const jsFiles = fs.readdirSync(webappPath).filter((file) => file.endsWith('.js'));
jsFiles.forEach((file) => {
const filePath = path.join(webappPath, file);
const content = fs.readFileSync(filePath, 'utf8')
.replace(/http:\/\/localhost:8080/g, process.env.DEPLOYMENT_URL || 'http://localhost:8080');
fs.writeFileSync(filePath, content);
});

app.use('/', express.static(webappPath));
app.use(bodyParser.json() as RequestHandler);
app.use(
Expand Down
2 changes: 1 addition & 1 deletion packages/webapp/webpack/webpack.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ module.exports = {
}),
new webpack.DefinePlugin({
'process.env.APPLICATION_SERVER_VERSION': JSON.stringify(process.env.APPLICATION_SERVER_VERSION || true),
'process.env.DEPLOYMENT_URL': JSON.stringify(process.env.DEPLOYMENT_URL || 'http://localhost:8888'),
'process.env.DEPLOYMENT_URL': JSON.stringify(process.env.DEPLOYMENT_URL || 'http://localhost:8080'),
'process.env.SENTRY_DSN': JSON.stringify(process.env.SENTRY_DSN || null),
'process.env.POSTHOG_HOST': JSON.stringify(process.env.POSTHOG_HOST || null),
'process.env.POSTHOG_KEY': JSON.stringify(process.env.POSTHOG_KEY || null),
Expand Down
2 changes: 1 addition & 1 deletion packages/webapp/webpack/webpack.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ module.exports = merge(common, {
devServer: {
static: path.join(__dirname, '../../build/webapp'),
host: '0.0.0.0',
port: 8888,
port: 8080,
proxy: [
{
context: ['/'],
Expand Down

0 comments on commit 06fd713

Please sign in to comment.