From 72c04d849522fe61cd625088275bf329354b737e Mon Sep 17 00:00:00 2001 From: 3wordchant <3wordchant@noreply.git.autonomic.zone> Date: Thu, 22 Jul 2021 01:18:10 +0200 Subject: [PATCH] Docker image, & local development docker-compose.yml (#2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds: - a Docker image, which can be used in both development and production - a `docker-compose.yml` file for local development (could probably be adapted for production deployments) Testing: - `git checkout docker` - `docker-compose up` - pray 🙏 - go to http://localhost:5000 ## App architecture I added the ability to load secret config variables (`HUB_TOKEN`, `STRIPE_SECRET_KEY` etc) from files, to support [Docker Secrets](https://docs.docker.com/engine/swarm/secrets/) natively. The code should probably log an error if a specified `HUB_TOKEN_FILE` doesn't exist instead of failing silently.. ## Docker architecture This uses a multi-stage build to reduce the size of the final image -- having pipenv install to a predefined virtualenv, and then copying that over. The compose file doesn't include a definition for a cron runner service, and I haven't tested running one yet. Here be dragons! You can rebuild the image locally using `docker-compose build`, but this isn't required for changes to the app code, only if you edit the `Dockerfile`, or want to publish your image for use on a swarm server (in which case you will need to edit the image name to put in your own Docker hub credentials). Currently, the image is rebuilt (should set up auto-tagging..) and published with every push to this 3wordchant/capsul-flask fork. Reviewed-on: https://git.autonomic.zone/3wordchant/capsul-flask/pulls/2 Co-authored-by: 3wordchant <3wordchant@noreply.git.autonomic.zone> Co-committed-by: 3wordchant <3wordchant@noreply.git.autonomic.zone> --- .drone.yml | 13 +++++++++++ Dockerfile | 48 +++++++++++++++++++++++++++++++++++++++++ capsulflask/__init__.py | 16 ++++++++++++++ docker-compose.yml | 36 +++++++++++++++++++++++++++++++ 4 files changed, 113 insertions(+) create mode 100644 .drone.yml create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..88fa8ac --- /dev/null +++ b/.drone.yml @@ -0,0 +1,13 @@ +--- +kind: pipeline +name: publish docker image +steps: + - name: build and publish + image: plugins/docker + settings: + username: + from_secret: docker_reg_username_3wc + password: + from_secret: docker_reg_passwd_3wc + repo: 3wordchant/capsul-flask + tags: ${DRONE_COMMIT_BRANCH} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e148126 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,48 @@ +FROM python:3.8-alpine as build + +RUN apk add --no-cache \ + build-base \ + gcc \ + gettext \ + git \ + jpeg-dev \ + libffi-dev \ + libjpeg \ + musl-dev \ + postgresql-dev \ + python3-dev \ + zlib-dev + +RUN mkdir -p /app/{code,venv} +WORKDIR /app/code +COPY Pipfile Pipfile.lock /app/code/ + +RUN python3 -m venv /app/venv +RUN pip install pipenv setuptools +ENV PATH="/app/venv/bin:$PATH" VIRTUAL_ENV="/app/venv" +RUN pip install wheel cppy +# Install dependencies into the virtual environment with Pipenv +RUN pipenv install --deploy --verbose + +FROM python:3.8-alpine + +RUN apk add --no-cache \ + cloud-utils \ + libjpeg \ + libpq \ + libstdc++ \ + libvirt-client \ + openssh-client \ + virt-install + +COPY . /app/code/ +WORKDIR /app/code + +COPY --from=build /app/venv /app/venv +ENV PATH="/app/venv/bin:$PATH" VIRTUAL_ENV="/app/venv" + +CMD ["gunicorn", "--bind", "0.0.0.0:5000", "-k", "gevent", "--worker-connections", "1000", "app:app"] + +VOLUME /app/code + +EXPOSE 5000 diff --git a/capsulflask/__init__.py b/capsulflask/__init__.py index 7aabe3f..ac258a2 100644 --- a/capsulflask/__init__.py +++ b/capsulflask/__init__.py @@ -27,8 +27,24 @@ class StdoutMockFlaskMail: def send(self, message: Message): current_app.logger.info(f"Email would have been sent if configured:\n\nto: {','.join(message.recipients)}\nsubject: {message.subject}\nbody:\n\n{message.body}\n\n") + load_dotenv(find_dotenv()) +for var_name in [ + "SPOKE_HOST_TOKEN", "HUB_TOKEN", "STRIPE_SECRET_KEY", + "BTCPAY_PRIVATE_KEY", "MAIL_PASSWORD" +]: + var = os.environ.get(f"{var_name}_FILE") + if not var: + continue + + if not os.path.isfile(var): + continue + + with open(var) as secret_file: + os.environ[var_name] = secret_file.read().rstrip('\n') + del os.environ[f"{var_name}_FILE"] + app = Flask(__name__) app.config.from_mapping( diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..8a62470 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,36 @@ +--- +version: "3.8" + +services: + app: + image: 3wordchant/capsul-flask:latest + build: . + volumes: + - "./:/app/code" + - "../tank:/tank" + # - "/var/run/libvirt/libvirt-sock:/var/run/libvirt/libvirt-sock" + depends_on: + - db + ports: + - "5000:5000" + environment: + - "POSTGRES_CONNECTION_PARAMETERS=host=db port=5432 user=capsul password=capsul dbname=capsul" + - SPOKE_MODEL=shell-scripts + #- FLASK_DEBUG=1 + - BASE_URL=http://localhost:5000 + - ADMIN_PANEL_ALLOW_EMAIL_ADDRESSES=3wc.capsul@doesthisthing.work + - VIRSH_DEFAULT_CONNECT_URI=qemu:///system + # The image uses gunicorn by default, let's override it with Flask's + # built-in development server + command: ["flask", "run", "-h", "0.0.0.0", "-p", "5000"] + db: + image: "postgres:9.6.5-alpine" + volumes: + - "postgres:/var/lib/postgresql/data" + environment: + POSTGRES_USER: capsul + POSTGRES_PASSWORD: capsul + POSTGRES_DB: capsul + +volumes: + postgres: