Skip to content

Commit c4181e6

Browse files
committed
Add GitHub Actions workflow to build run-mooc-jutut images
1 parent 2751851 commit c4181e6

13 files changed

Lines changed: 435 additions & 0 deletions

File tree

.github/workflows/build.yml

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
name: Build and push Docker image
2+
3+
on:
4+
push:
5+
tags:
6+
- '[0-9]*'
7+
8+
jobs:
9+
build:
10+
name: Build (${{ matrix.platform }})
11+
runs-on: ${{ matrix.runner }}
12+
strategy:
13+
fail-fast: false
14+
matrix:
15+
include:
16+
- platform: linux/amd64
17+
runner: ubuntu-24.04
18+
- platform: linux/arm64
19+
runner: ubuntu-24.04-arm
20+
21+
steps:
22+
- uses: actions/checkout@v4
23+
24+
- name: Log in to Docker Hub
25+
uses: docker/login-action@v3
26+
with:
27+
username: ${{ secrets.DOCKERHUB_USERNAME }}
28+
password: ${{ secrets.DOCKERHUB_TOKEN }}
29+
30+
- name: Set up Docker Buildx
31+
uses: docker/setup-buildx-action@v3
32+
33+
# Build and push platform-specific digest (no manifest tag yet)
34+
- name: Build and push by digest
35+
id: build
36+
uses: docker/build-push-action@v6
37+
with:
38+
context: .
39+
file: docker/Dockerfile
40+
platforms: ${{ matrix.platform }}
41+
push: true
42+
outputs: type=image,name=apluslms/run-mooc-jutut,push-by-digest=true,name-canonical=true
43+
44+
# Save the digest so the merge job can find it
45+
- name: Export digest
46+
run: |
47+
mkdir -p /tmp/digests
48+
digest="${{ steps.build.outputs.digest }}"
49+
touch "/tmp/digests/${digest#sha256:}"
50+
51+
- name: Upload digest artifact
52+
uses: actions/upload-artifact@v4
53+
with:
54+
name: digest-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}
55+
path: /tmp/digests/*
56+
retention-days: 1
57+
58+
merge:
59+
name: Merge manifests
60+
runs-on: ubuntu-24.04
61+
needs: build
62+
63+
steps:
64+
- name: Download digests
65+
uses: actions/download-artifact@v4
66+
with:
67+
path: /tmp/digests
68+
pattern: digest-*
69+
merge-multiple: true
70+
71+
- name: Log in to Docker Hub
72+
uses: docker/login-action@v3
73+
with:
74+
username: ${{ secrets.DOCKERHUB_USERNAME }}
75+
password: ${{ secrets.DOCKERHUB_TOKEN }}
76+
77+
- name: Set up Docker Buildx
78+
uses: docker/setup-buildx-action@v3
79+
80+
- name: Determine tags
81+
id: meta
82+
uses: docker/metadata-action@v5
83+
with:
84+
images: apluslms/run-mooc-jutut
85+
tags: |
86+
type=raw,value=latest
87+
type=match,pattern=(\d+\.\d+)\.\d+$,group=1
88+
89+
- name: Create and push multi-arch manifest
90+
working-directory: /tmp/digests
91+
run: |
92+
docker buildx imagetools create \
93+
$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
94+
$(printf 'apluslms/run-mooc-jutut@sha256:%s ' *)

docker/Dockerfile

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
FROM apluslms/service-base:django-1.19
2+
3+
# Set container related configuration via environment variables
4+
ENV CONTAINER_TYPE="jutut" \
5+
JUTUT_LOCAL_SETTINGS="/srv/jutut-cont-settings.py" \
6+
JUTUT_SECRET_KEY_FILE="/local/jutut/secret_key.py"
7+
8+
RUN adduser --system --no-create-home --disabled-password --gecos "MOOC Jutut webapp server,,," --home /srv/jutut --ingroup nogroup jutut \
9+
&& mkdir /srv/jutut && chown jutut.nogroup /srv/jutut \
10+
&& git config --global --add safe.directory /srv/jutut \
11+
&& :
12+
13+
COPY docker/rootfs /
14+
COPY . /srv/jutut
15+
16+
RUN cd /srv/jutut \
17+
# 1) prebuild .pyc files
18+
&& python3 -m compileall -q . \
19+
\
20+
# 2) install requirements, remove the file, remove unrequired locales and tests
21+
&& pip_install -r requirements.txt \
22+
&& pip_install -r requirements_testing.txt \
23+
&& pip_install "django-debug-toolbar >= 6.1.0, < 7" \
24+
&& rm requirements.txt requirements_testing.txt \
25+
&& find /usr/local/lib/python* -type d -regex '.*/locale/[a-z_A-Z]+' -not -regex '.*/\(en\|fi\|sv\)' -print0 | xargs -0 rm -rf \
26+
&& find /usr/local/lib/python* -type d -name 'tests' -print0 | xargs -0 rm -rf \
27+
\
28+
# 3) preprocess
29+
&& export \
30+
JUTUT_SECRET_KEY="-" \
31+
JUTUT_CACHES="{\"default\": {\"BACKEND\": \"django.core.cache.backends.dummy.DummyCache\"}}" \
32+
&& python3 manage.py compilemessages 2>&1 \
33+
&& create-db.sh jutut jutut django-migrate.sh \
34+
&& :
35+
36+
EXPOSE 8082
37+
WORKDIR /srv/jutut/
38+
CMD [ "manage", "runserver", "0.0.0.0:8082" ]

docker/README.md

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# run-mooc-jutut
2+
3+
Docker container for running [MOOC-Jutut](https://github.com/apluslms/mooc-jutut).
4+
This is intended for local development and testing.
5+
6+
# Usage
7+
8+
MOOC-jutut is installed in `/srv/jutut`.
9+
You can mount development version of the source code to `/src/jutut`.
10+
The container will then copy it to `/srv/jutut` and compile
11+
the translation file (django.mo). If you mount directly to
12+
`/srv/jutut`, you need to manually compile the translation file beforehand,
13+
but on the other hand, Django can reload the code and restart the server
14+
without restarting the whole container when you edit the source code files.
15+
16+
You can mount development version of the MOOC-Jutut source code on top of that, if you wish.
17+
18+
Location `/data` is a volume and contains the database and secret key.
19+
It is world writable, so you can run this container as normal user.
20+
21+
You may enable the [Django Debug Toolbar](https://pypi.org/project/django-debug-toolbar/)
22+
(a set of panels in the web browser that display useful debug information)
23+
by setting the Django setting `ENABLE_DJANGO_DEBUG_TOOLBAR` to `True`.
24+
It is easy to do that via the environment variable `JUTUT_ENABLE_DJANGO_DEBUG_TOOLBAR`.
25+
See the `docker-compose.yml` example below.
26+
27+
Using the VS Code Python debugger is easy with the container.
28+
VS Code can attach to the container even when VS Code is running in the host machine
29+
and the Jutut app inside a container. In the `docker-compose.yml` example below,
30+
the `command` starts debugpy inside the container.
31+
The VS Code debugging settings are given in the `launch.json` example further below.
32+
33+
Partial example of `docker-compose.yml` (volumes are optional of course):
34+
35+
```yaml
36+
services:
37+
jutut:
38+
image: apluslms/run-mooc-jutut:3.1
39+
# Start debugpy when you want to debug remotely in VS Code.
40+
#command: "python3 -m debugpy --wait-for-client --listen 0.0.0.0:5678 manage.py runserver 0.0.0.0:8082"
41+
#command: "python3 -m debugpy --listen 0.0.0.0:5678 manage.py runserver 0.0.0.0:8082"
42+
environment:
43+
JUTUT_ENABLE_DJANGO_DEBUG_TOOLBAR: 'false'
44+
volumes:
45+
# named persistent volume (until removed)
46+
- data:/data
47+
# mount development version to /src/jutut
48+
#- /home/user/mooc-jutut/:/src/jutut/:ro
49+
# or to /srv/jutut
50+
#- /home/user/mooc-jutut/:/srv/jutut/:ro
51+
ports:
52+
- "8082:8082"
53+
# port 5678 inside the container is used by debugpy (VS Code debugger)
54+
- "5677:5678"
55+
56+
grader:
57+
image: apluslms/run-mooc-grader:1.31
58+
#command: "python3 -m debugpy --wait-for-client --listen 0.0.0.0:5678 manage.py runserver 0.0.0.0:8080"
59+
volumes:
60+
- data:/data
61+
- /var/run/docker.sock:/var/run/docker.sock
62+
- /tmp/aplus:/tmp/aplus
63+
# mount a course directory
64+
#- $HOME/courses/aplus-manual:/srv/courses/default:ro
65+
#- $HOME/mooc-grader/:/srv/grader/:ro
66+
ports:
67+
- "8080:8080"
68+
- "5679:5678"
69+
plus:
70+
image: apluslms/run-aplus-front:1.31
71+
#command: "python3 -m debugpy --wait-for-client --listen 0.0.0.0:5678 manage.py runserver 0.0.0.0:8000"
72+
environment:
73+
APLUS_ENABLE_DJANGO_DEBUG_TOOLBAR: 'false'
74+
volumes:
75+
- data:/data
76+
#- $HOME/a-plus/:/srv/aplus/:ro
77+
ports:
78+
- "8000:8000"
79+
- "5678:5678"
80+
depends_on:
81+
- grader
82+
83+
volumes:
84+
data:
85+
```
86+
87+
VS Code debugger settings `launch.json`.
88+
Place in `mooc-jutut/.vscode/launch.json` (in the mooc-jutut source code directory).
89+
90+
```
91+
{
92+
// Use IntelliSense to learn about possible attributes.
93+
// Hover to view descriptions of existing attributes.
94+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
95+
"version": "0.2.0",
96+
"configurations": [
97+
{
98+
"name": "Remote Attach: Jutut Python",
99+
"type": "python",
100+
"request": "attach",
101+
"connect": {
102+
"host": "localhost",
103+
"port": 5677
104+
},
105+
"pathMappings": [
106+
{
107+
"localRoot": "${workspaceFolder}",
108+
"remoteRoot": "/srv/jutut"
109+
}
110+
],
111+
"justMyCode": false
112+
}
113+
]
114+
}
115+
```
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/bin/sh
2+
3+
. /usr/local/lib/cont-init-functions.sh
4+
ENSURE_DIR_MODE=2755
5+
ENSURE_DIR_USER=jutut
6+
ENSURE_DIR_GROUP=nogroup
7+
8+
ensure_dir /run/jutut
9+
ensure_dir /local/jutut
10+
ensure_dir /local/jutut/static
11+
ensure_dir /local/jutut/media
12+
13+
# Take this container's IP (v4) address, change the last component to .1 and
14+
# output it in a JSON list (like ["127.0.0.1"]) to Jutut settings.py.
15+
# The IP address with the .1 ending should be the host machine's IP address in
16+
# the Docker network, in other words, the IP address of the client (web browser).
17+
echo '["'$(hostname -i | cut -s -d. -f1-3)'.1"]' > /var/run/s6/container_environment/JUTUT_INTERNAL_IPS
18+

docker/rootfs/etc/services.d/jutut-celery-beat/down

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/usr/local/lib/prefix-logs
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/bin/execlineb -P
2+
3+
define home /srv/jutut
4+
define run /run/jutut
5+
fdmove -c 2 1
6+
7+
# Use python3 from venv, if it exists
8+
# (-s is undocumented magic, that seems to move _prog_ to end of _then_ or
9+
# _else_ blocks and thus makes _define_ work)
10+
ifthenelse -s { test -e /local/venv_jutut/bin/python3 }
11+
{ define python3 /local/venv_jutut/bin/python3 }
12+
{ define python3 python3 }
13+
14+
# wait for dependencies
15+
#if { s6-svwait -t 2000 -U /var/run/s6/services/postgresql/ }
16+
#if { s6-svwait -t 2000 -U /var/run/s6/services/rabbitmq/ }
17+
18+
# Use container environment
19+
with-contenv
20+
21+
# user and workdir
22+
s6-setuidgid jutut
23+
s6-env HOME=${home}
24+
cd ${home}
25+
26+
# run daemon
27+
${python3} -m celery
28+
--app jutut
29+
beat
30+
--pidfile=${run}/celery-beat.pid
31+
--loglevel=info
32+
--schedule=/local/jutut/celery-beat-schedule

docker/rootfs/etc/services.d/jutut-celery-worker/down

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/usr/local/lib/prefix-logs
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/bin/execlineb -P
2+
3+
define home /srv/jutut
4+
define run /run/jutut
5+
fdmove -c 2 1
6+
7+
# Use python3 from venv, if it exists
8+
# (-s is undocumented magic, that seems to move _prog_ to end of _then_ or
9+
# _else_ blocks and thus makes _define_ work)
10+
ifthenelse -s { test -e /local/venv_jutut/bin/python3 }
11+
{ define python3 /local/venv_jutut/bin/python3 }
12+
{ define python3 python3 }
13+
14+
# Wait for dependencies
15+
#if { s6-svwait -t 2000 -U /var/run/s6/services/postgresql/ }
16+
#if { s6-svwait -t 2000 -U /var/run/s6/services/rabbitmq/ }
17+
18+
# Use container environment
19+
with-contenv
20+
21+
# user and workdir
22+
s6-setuidgid jutut
23+
s6-env HOME=${home}
24+
cd ${home}
25+
26+
# run daemon
27+
${python3} -m celery
28+
--app jutut
29+
worker
30+
--pidfile=${run}/celery-worker.pid
31+
--loglevel=info
32+
--autoscale=6,1

0 commit comments

Comments
 (0)