Add Docker support for FoodsharingSiegen project
Introduced Docker setup, including `docker-compose.yml`, `dockerfile.server`, and related Python helper scripts (`image-create.py`, `image-push.py`, `publish-project.py`, `publish-aio.py`) for building, managing, and deploying Docker images. Updated `.gitignore` to exclude published files, and renamed a field label in the Blazor component for better clarity.
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -2,4 +2,6 @@ bin/
|
||||
obj/
|
||||
/packages/
|
||||
riderModule.iml
|
||||
/_ReSharper.Caches/
|
||||
/_ReSharper.Caches/
|
||||
|
||||
Publish/
|
||||
10
Docker/docker-compose.yml
Normal file
10
Docker/docker-compose.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
services:
|
||||
server:
|
||||
container_name: fs-onboarding
|
||||
hostname: fs-onboarding
|
||||
image: ghcr.io/troogs/fs-onboarding/server:latest
|
||||
ports:
|
||||
- "8100:56000"
|
||||
volumes:
|
||||
- /docker/data/fs-onboarding/config:/app/config/
|
||||
- /docker/data/fs-onboarding/data:/app/data/
|
||||
11
Docker/dockerfile.server
Normal file
11
Docker/dockerfile.server
Normal file
@@ -0,0 +1,11 @@
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
|
||||
RUN apt-get update && apt-get install iputils-ping -y
|
||||
|
||||
LABEL org.opencontainers.image.source=https://github.com/TroogS/FoodsharingSiegen
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the Blazor Server published files from the Publish folder relative to the build context
|
||||
COPY ../Publish/Server/ .
|
||||
|
||||
ENTRYPOINT ["dotnet", "FoodsharingSiegen.Server.dll"]
|
||||
63
Docker/image-create.py
Normal file
63
Docker/image-create.py
Normal file
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python3
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
|
||||
def image_exists_on_server(full_image_tag):
|
||||
"""
|
||||
Check if a Docker image exists on the remote server.
|
||||
Uses 'docker manifest inspect' which returns 0 if the image is found.
|
||||
Debug output is printed for inspection.
|
||||
"""
|
||||
try:
|
||||
subprocess.run(
|
||||
["docker", "manifest", "inspect", full_image_tag],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
print(f"[DEBUG] Found image on server: {full_image_tag}")
|
||||
return True
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"[DEBUG] Image not found on server: {full_image_tag}. Error: {e.stderr.strip()}")
|
||||
return False
|
||||
|
||||
def get_date_tag(base_tag, today):
|
||||
"""
|
||||
Given the base image tag (e.g. 'ghcr.io/troogs/fs-onboarding/server')
|
||||
and today's date, find the first available tag in the format YYYYMMDD-i
|
||||
that is not already present on the remote server.
|
||||
"""
|
||||
i = 1
|
||||
while True:
|
||||
date_tag = f"{today}-{i}"
|
||||
full_tag = f"{base_tag}:{date_tag}"
|
||||
print(f"[DEBUG] Checking tag {date_tag}: {full_tag}")
|
||||
|
||||
if not image_exists_on_server(full_tag):
|
||||
print(f"[DEBUG] Tag {date_tag} is available for use.")
|
||||
return date_tag
|
||||
else:
|
||||
print(f"[DEBUG] Tag {date_tag} is already in use. Incrementing index.")
|
||||
i += 1
|
||||
|
||||
def main():
|
||||
# Get today's date in YYYYMMDD format.
|
||||
today = datetime.now().strftime("%Y%m%d")
|
||||
print(f"Using date tag base: {today}")
|
||||
|
||||
base_image_tag = "ghcr.io/troogs/fs-onboarding/server"
|
||||
date_tag = get_date_tag(base_image_tag, today)
|
||||
print(f"Common date tag determined: {date_tag}")
|
||||
|
||||
# Build command using the common date tag for the server image.
|
||||
command = (
|
||||
f"docker build -f dockerfile.server "
|
||||
f"-t {base_image_tag}:latest "
|
||||
f"-t {base_image_tag}:{date_tag} ..\\"
|
||||
)
|
||||
print(f"Executing: {command}")
|
||||
subprocess.run(command, shell=True, check=True)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
65
Docker/image-push.py
Normal file
65
Docker/image-push.py
Normal file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
import subprocess
|
||||
import re
|
||||
|
||||
def get_latest_timestamp_tag():
|
||||
"""
|
||||
Searches local images for tags matching the pattern:
|
||||
ghcr.io/troogs/fs-onboarding/server:YYYYMMDD-i
|
||||
and returns the latest timestamp tag (YYYYMMDD-i) based on date and index.
|
||||
"""
|
||||
# Regular expression to match tags like: ghcr.io/troogs/fs-onboarding/server:20250328-1
|
||||
pattern = re.compile(r"ghcr\.io/troogs/fs-onboarding/server:(\d{8})-(\d+)$")
|
||||
|
||||
# List images for the repository using the docker images command.
|
||||
result = subprocess.run(
|
||||
["docker", "images", "ghcr.io/troogs/fs-onboarding/server", "--format", "{{.Repository}}:{{.Tag}}"],
|
||||
stdout=subprocess.PIPE,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
|
||||
tags = result.stdout.strip().splitlines()
|
||||
timestamp_tags = []
|
||||
for tag in tags:
|
||||
m = pattern.match(tag)
|
||||
if m:
|
||||
date_part, index = m.groups()
|
||||
timestamp_tags.append((date_part, int(index)))
|
||||
|
||||
if not timestamp_tags:
|
||||
return None
|
||||
|
||||
# Choose the maximum tuple; lexicographical order works for YYYYMMDD and then by numeric index.
|
||||
latest = max(timestamp_tags, key=lambda x: (x[0], x[1]))
|
||||
return f"{latest[0]}-{latest[1]}"
|
||||
|
||||
def push_image(image_tag):
|
||||
"""
|
||||
Pushes the specified image tag to the container registry.
|
||||
"""
|
||||
print(f"Pushing image: {image_tag}")
|
||||
subprocess.run(["docker", "push", image_tag], check=True)
|
||||
|
||||
def main():
|
||||
# Retrieve the latest timestamp tag for the server image.
|
||||
server_timestamp = get_latest_timestamp_tag()
|
||||
|
||||
if server_timestamp is None:
|
||||
print("Error: Could not find timestamp tagged images for server.")
|
||||
return
|
||||
|
||||
common_timestamp = server_timestamp
|
||||
print(f"Common timestamp tag identified: {common_timestamp}")
|
||||
|
||||
# Prepare the list of images to push: both 'latest' and the timestamp-tagged images.
|
||||
images_to_push = [
|
||||
"ghcr.io/troogs/fs-onboarding/server:latest",
|
||||
f"ghcr.io/troogs/fs-onboarding/server:{common_timestamp}"
|
||||
]
|
||||
|
||||
for image in images_to_push:
|
||||
push_image(image)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
15
Docker/publish-aio.py
Normal file
15
Docker/publish-aio.py
Normal file
@@ -0,0 +1,15 @@
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
# List of scripts to run in order
|
||||
scripts = ['publish-project.py', 'image-create.py', 'image-push.py']
|
||||
|
||||
for script in scripts:
|
||||
print(f"Executing {script}...")
|
||||
try:
|
||||
# Run the script using the current Python interpreter
|
||||
subprocess.run([sys.executable, script], check=True)
|
||||
print(f"{script} executed successfully.\n")
|
||||
except subprocess.CalledProcessError as error:
|
||||
print(f"Error: {script} failed with exit code {error.returncode}.")
|
||||
break
|
||||
38
Docker/publish-project.py
Normal file
38
Docker/publish-project.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
def main():
|
||||
# Determine the absolute path of this script's directory
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
# Compute the path to the FoodsharingSiegen.Server project directory
|
||||
# This assumes the following structure:
|
||||
# files/
|
||||
# Docker/ <-- this script resides here
|
||||
# FoodsharingSiegen.Server/
|
||||
project_dir = os.path.normpath(os.path.join(script_dir, '..', 'FoodsharingSiegen.Server'))
|
||||
|
||||
# Check if the project directory exists
|
||||
if not os.path.isdir(project_dir):
|
||||
print(f"Error: Project directory not found: {project_dir}")
|
||||
sys.exit(1)
|
||||
|
||||
# Prepare the dotnet publish command
|
||||
command = ["dotnet", "publish", project_dir]
|
||||
print("Running command:", " ".join(command))
|
||||
|
||||
# Execute the command and capture the output
|
||||
try:
|
||||
result = subprocess.run(command, capture_output=True, text=True, check=True)
|
||||
print(result.stdout)
|
||||
if result.stderr:
|
||||
print("Warnings/Errors:", result.stderr, file=sys.stderr)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("An error occurred while publishing the dotnet project.")
|
||||
print(e.stdout)
|
||||
print(e.stderr, file=sys.stderr)
|
||||
sys.exit(e.returncode)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -23,7 +23,7 @@
|
||||
</div>
|
||||
|
||||
<Field>
|
||||
<FieldLabel>Memo (optional)</FieldLabel>
|
||||
<FieldLabel>Info (optional)</FieldLabel>
|
||||
<TextEdit @bind-Text="Prospect.Memo" Placeholder="Beliebige Info"></TextEdit>
|
||||
</Field>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user