Skip to content

Commit 9f72adf

Browse files
committed
Initial commit.
Signed-off-by: Chad Metcalf <[email protected]>
1 parent c17194c commit 9f72adf

File tree

14 files changed

+429
-1
lines changed

14 files changed

+429
-1
lines changed

.github/workflows/main-ci.yml

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
name: Main Branch CI
2+
3+
# For all pushes to the main branch run the tests and push the image to the
4+
# GitHub registry under an edge tag so we can use it for the nightly
5+
# integration tests
6+
on:
7+
push:
8+
branches: main
9+
10+
jobs:
11+
docker:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Checkout
15+
uses: actions/checkout@v2
16+
- name: Prepare
17+
id: prep
18+
run: |
19+
DOCKER_IMAGE=ghcr.io/metcalfc/docker-action-examples
20+
VERSION=edge
21+
if [[ $GITHUB_REF == refs/tags/* ]]; then
22+
VERSION=${GITHUB_REF#refs/tags/v}
23+
fi
24+
if [ "${{ github.event_name }}" = "schedule" ]; then
25+
VERSION=nightly
26+
fi
27+
TAGS="${DOCKER_IMAGE}:${VERSION}"
28+
if [[ $VERSION =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
29+
TAGS="$TAGS,${DOCKER_IMAGE}:latest"
30+
fi
31+
echo ::set-output name=tags::${TAGS}
32+
33+
- name: Set up Docker Buildx
34+
id: buildx
35+
uses: docker/setup-buildx-action@master
36+
37+
- name: Cache Docker layers
38+
uses: actions/cache@v2
39+
with:
40+
path: /tmp/.buildx-cache
41+
key: ${{ runner.os }}-buildx-${{ github.sha }}
42+
restore-keys: |
43+
${{ runner.os }}-buildx-
44+
45+
- name: Login to ghcr
46+
if: github.event_name != 'pull_request'
47+
uses: docker/login-action@v1
48+
with:
49+
registry: ghcr.io
50+
username: ${{ github.repository_owner }}
51+
password: ${{ secrets.ghcr_TOKEN }}
52+
53+
- name: Test
54+
id: docker_test
55+
uses: docker/build-push-action@v2-build-push
56+
with:
57+
builder: ${{ steps.buildx.outputs.name }}
58+
context: ./app
59+
file: ./app/Dockerfile
60+
target: test
61+
62+
- name: Build and push
63+
id: docker_build
64+
uses: docker/build-push-action@v2-build-push
65+
with:
66+
builder: ${{ steps.buildx.outputs.name }}
67+
context: ./app
68+
file: ./app/Dockerfile
69+
target: prod
70+
push: ${{ github.event_name != 'pull_request' }}
71+
tags: ${{ steps.prep.outputs.tags }}

.github/workflows/nightly.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Main Branch CI
2+
3+
# Run the nightly tests at at 2 AM UTC
4+
on:
5+
schedule:
6+
- cron: "0 2 * * *"
7+
jobs:
8+
docker:
9+
runs-on: ubuntu-latest
10+
env:
11+
PROD_IMAGE: ghcr.io/metcalfc/docker-action-examples:edge
12+
steps:
13+
- name: Checkout
14+
uses: actions/checkout@v2
15+
- name: Compose up
16+
run: docker-compose -f docker-compose.yml up -d
17+
- name: Check running containers
18+
run: docker ps -a
19+
- name: Check logs
20+
run: docker logs web
21+
- name: Compose down
22+
run: docker-compose -f docker-compose.yml down

.github/workflows/pr-ci.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Main Branch CI
2+
3+
# For all pushes to the main branch run the tests and push the image to the
4+
# GitHub registry under an edge tag so we can use it for the nightly
5+
# integration tests
6+
on:
7+
pull_request:
8+
branches: main
9+
10+
jobs:
11+
docker:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Checkout
15+
uses: actions/checkout@v2
16+
17+
- name: Set up Docker Buildx
18+
id: buildx
19+
uses: docker/setup-buildx-action@master
20+
21+
- name: Cache Docker layers
22+
uses: actions/cache@v2
23+
with:
24+
path: /tmp/.buildx-cache
25+
key: ${{ runner.os }}-buildx-${{ github.sha }}
26+
restore-keys: |
27+
${{ runner.os }}-buildx-
28+
29+
- name: Test
30+
id: docker_test
31+
uses: docker/build-push-action@v2-build-push
32+
with:
33+
builder: ${{ steps.buildx.outputs.name }}
34+
context: ./app
35+
file: ./app/Dockerfile
36+
target: test

.github/workflows/release.yml

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
name: Publish Releases to Hub
2+
3+
# When its time to do a release do a full cross platform build for all supported
4+
# architectures and push all of them to Docker Hub.
5+
# Only trigger on semver shaped tags.
6+
on:
7+
push:
8+
tags:
9+
- "v*.*.*"
10+
11+
jobs:
12+
docker:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Checkout
16+
uses: actions/checkout@v2
17+
18+
- name: Prepare
19+
id: prep
20+
run: |
21+
DOCKER_IMAGE=metcalfc/docker-action-examples
22+
VERSION=edge
23+
if [[ $GITHUB_REF == refs/tags/* ]]; then
24+
VERSION=${GITHUB_REF#refs/tags/v}
25+
fi
26+
if [ "${{ github.event_name }}" = "schedule" ]; then
27+
VERSION=nightly
28+
fi
29+
TAGS="${DOCKER_IMAGE}:${VERSION}"
30+
if [[ $VERSION =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
31+
TAGS="$TAGS,${DOCKER_IMAGE}:latest"
32+
fi
33+
echo ::set-output name=tags::${TAGS}
34+
35+
- name: Set up QEMU
36+
uses: docker/setup-qemu-action@master
37+
with:
38+
platforms: all
39+
40+
- name: Set up Docker Buildx
41+
id: buildx
42+
uses: docker/setup-buildx-action@master
43+
44+
- name: Cache Docker layers
45+
uses: actions/cache@v2
46+
with:
47+
path: /tmp/.buildx-cache
48+
key: ${{ runner.os }}-buildx-${{ github.sha }}
49+
restore-keys: |
50+
${{ runner.os }}-buildx-
51+
52+
- name: Login to DockerHub
53+
if: github.event_name != 'pull_request'
54+
uses: docker/login-action@v1
55+
with:
56+
username: ${{ secrets.DOCKER_USERNAME }}
57+
password: ${{ secrets.DOCKER_PASSWORD }}
58+
59+
- name: Build and push
60+
id: docker_build
61+
uses: docker/build-push-action@v2-build-push
62+
with:
63+
builder: ${{ steps.buildx.outputs.name }}
64+
context: ./app
65+
file: ./app/Dockerfile
66+
target: prod
67+
platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x
68+
push: ${{ github.event_name != 'pull_request' }}
69+
tags: ${{ steps.prep.outputs.tags }}
70+
cache-from: type=local,src=/tmp/.buildx-cache
71+
cache-to: type=local,dest=/tmp/.buildx-cache
72+
73+
- name: Image digest
74+
run: echo ${{ steps.docker_build.outputs.digest }}

Makefile

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# BuildKit is a next generation container image builder. You can enable it using
2+
# an environment variable or using the Engine config, see:
3+
# https://docs.docker.com/develop/develop-images/build_enhancements/#to-enable-buildkit-builds
4+
export DOCKER_BUILDKIT=1
5+
6+
GIT_TAG?=$(shell git describe --tags --match "v[0-9]*" 2> /dev/null)
7+
ifeq ($(GIT_TAG),)
8+
GIT_TAG=edge
9+
endif
10+
11+
# Docker image tagging:
12+
HUB_USER?=${USER}
13+
REPO?=$(shell basename ${PWD})
14+
TAG?=${GIT_TAG}
15+
DEV_IMAGE?=${REPO}:latest
16+
PROD_IMAGE?=${HUB_USER}/${REPO}:${TAG}
17+
18+
# Local development happens here!
19+
# This starts your application and bind mounts the source into the container so
20+
# that changes are reflected in real time.
21+
# Once you see the message "Running on http://0.0.0.0:5000/", open a Web browser at
22+
# http://localhost:5000
23+
.PHONY: dev
24+
all: dev
25+
dev:
26+
@COMPOSE_DOCKER_CLI_BUILD=1 docker-compose -f docker-compose.dev.yml up --build
27+
28+
# Run the unit tests.
29+
.PHONY: build-test unit-test test
30+
unit-test:
31+
@docker build --progress plain --target test ./app
32+
33+
test: unit-test
34+
35+
# Build a production image for the application.
36+
.PHONY: build
37+
build:
38+
@docker build --target prod --tag ${PROD_IMAGE} ./app
39+
40+
# Push the production image to a registry.
41+
.PHONY: push
42+
push: build
43+
@docker push ${PROD_IMAGE}
44+
45+
# Run the production image either via compose or run
46+
.PHONY: deploy run
47+
deploy: build
48+
@PROD_IMAGE=${PROD_IMAGE} docker-compose up -d
49+
50+
run: build
51+
@docker run -d -p 5000:5000 ${PROD_IMAGE}
52+
53+
# Remove the dev container, dev image, test image, and clear the builder cache.
54+
.PHONY: clean
55+
clean:
56+
@docker-compose -f docker-compose.dev.yml down
57+
@docker rmi ${DEV_IMAGE} || true
58+
@docker builder prune --force --filter type=exec.cachemount --filter=unused-for=24h

README.md

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,106 @@
1-
# ghrc-example
1+
# Docker GitHub Action Example
2+
3+
Welcome. This is a simple example application to show a common Docker specific
4+
GitHub Action setup. We have a Python Flask application that is built and
5+
deployed in Docker containers using Dockerfiles and Docker Compose.
6+
7+
We want to setup CI to test:
8+
9+
- ✒ Every commit to `main`
10+
- ✉ Every PR
11+
- 🌃 Integration tests nightly
12+
- 🐳 Releases via tags pushed to Docker Hub.
13+
14+
We are going to use GitHub Actions for the CI infrastructure. Since its local to
15+
GitHub Actions and free when used inside GitHub Actions we're going to use the
16+
new GitHub Container Registry to hold a copy of the nightly Docker image.
17+
18+
After CI when it comes time for production we want to use Docker's new Amazon
19+
ECS integration to deploy from Docker Compose directly to Amazon ECS with
20+
Fargate. So we will push our release tagged images to Docker Hub which is
21+
integrated directly Amazon ECS via Docker Compose.
22+
23+
The Dockerfile is setup to use multi stage builds. We have stages for `test` and
24+
`prod`. This means we'll need Docker Buildx and we can use the a preview of the
25+
new Docker Buildx Action. This is going to let us achieve a couple awesome outcomes:
26+
27+
- We are going to use the buildx backend by default. Buildx out of the box brings a
28+
number of improvements over the default `docker build`.
29+
- We are going to setup buildx caching to take advantage of the GitHub Action Cache.
30+
You should see build performance improvements when repeating builds with common
31+
layers.
32+
- We are going to setup QEMU to do cross platform builds. In the example, we'll
33+
build this application for every Linux architecture that Docker Hub supports:
34+
- linux/386
35+
- linux/amd64
36+
- linux/arm/v6
37+
- linux/arm/v7
38+
- linux/arm64
39+
- linux/ppc64le
40+
- linux/s390x
41+
42+
I'm not going to have GitHub Action manage the deployment side of this example.
43+
Mostly because I don't want to leave an Amazon ECS cluster running.
44+
45+
## Compose sample application
46+
47+
### Python/Flask application
48+
49+
Project structure:
50+
51+
```
52+
.
53+
├── docker-compose.yaml
54+
├── app
55+
   ├── Dockerfile
56+
   ├── requirements.txt
57+
   └── app.py
58+
59+
```
60+
61+
[_docker-compose.yaml_](docker-compose.yaml)
62+
63+
```
64+
services:
65+
web:
66+
build: app
67+
ports:
68+
- '5000:5000'
69+
```
70+
71+
## Deploy with docker-compose
72+
73+
```
74+
$ docker-compose up -d
75+
Creating network "flask_default" with the default driver
76+
Building web
77+
Step 1/6 : FROM python:3.7-alpine
78+
...
79+
...
80+
Status: Downloaded newer image for python:3.7-alpine
81+
Creating flask_web_1 ... done
82+
83+
```
84+
85+
## Expected result
86+
87+
Listing containers must show one container running and the port mapping as below:
88+
89+
```
90+
$ docker ps
91+
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
92+
c126411df522 flask_web "python3 app.py" About a minute ago Up About a minute 0.0.0.0:5000->5000/tcp flask_web_1
93+
```
94+
95+
After the application starts, navigate to `http://localhost:5000` in your web browser or run:
96+
97+
```
98+
$ curl localhost:5000
99+
Hello World!
100+
```
101+
102+
Stop and remove the containers
103+
104+
```
105+
$ docker-compose down
106+
```

app/Dockerfile

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
FROM python:3.7-alpine AS base
2+
WORKDIR /app
3+
COPY requirements.txt /app
4+
RUN pip3 install -r requirements.txt
5+
6+
FROM base as src
7+
COPY . /app
8+
9+
FROM src as test
10+
COPY requirements.dev.txt /app
11+
RUN pip3 install -r requirements.dev.txt
12+
RUN python3 -m pytest
13+
14+
FROM src as prod
15+
ENTRYPOINT ["python3"]
16+
CMD ["app.py"]

0 commit comments

Comments
 (0)