Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docker deployment via GitHub Actions #115

Merged
merged 24 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading