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

Adding full docker development (& prod) files, including docker-compose, and updating to 12-factor #1

Merged
merged 2 commits into from
Apr 27, 2022
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
77 changes: 77 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Multi-stage unique docker build script, provides both dev & prod environments

# ----------------------------------------------------
# Base-image
# ----------------------------------------------------
FROM python:3.9-slim-buster as common-base
# Django directions: https://blog.ploetzli.ch/2020/efficient-multi-stage-build-django-docker/
# Pip on docker : https://pythonspeed.com/articles/multi-stage-docker-python/
# https://blog.mikesir87.io/2018/07/leveraging-multi-stage-builds-single-dockerfile-dev-prod/
# https://pythonspeed.com/articles/base-image-python-docker-images/

# Default environment: Dev
ARG ENV=dev

ENV PYTHONFAULTHANDLER=1 \
PYTHONUNBUFFERED=1 \
PYTHONHASHSEED=random \
PIP_NO_CACHE_DIR=off \
PIP_DISABLE_PIP_VERSION_CHECK=on \
PIP_DEFAULT_TIMEOUT=100

ENV HOST=0.0.0.0 \
PORT=8000

WORKDIR /app

COPY ./docker/install-packages.sh .
RUN ./install-packages.sh

# ----------------------------------------------------
# Install dependencies
# ----------------------------------------------------
FROM common-base AS dependencies
ENV PATH="/opt/venv/bin:$PATH"

# apt-get install build-essential -y
COPY requirements.txt /app/

RUN pip install --target /opt/packages -r requirements.txt

# ----------------------------------------------------
# Copy project
# ----------------------------------------------------
FROM common-base AS app-run
COPY --from=dependencies /opt/packages /opt/packages
ENV PYTHONPATH "${PYTHONPATH}:/opt/packages"
# ENV PYTHONPATH="$PYTHONPATH:/app/lemarche:/app/config"
COPY ./ark ./ark
COPY ./ark_import ./ark_import
COPY ./arklet ./arklet
COPY ./manage.py ./manage.py
COPY ./docker/entrypoint.sh ./entrypoint.sh

# ----------------------------------------------------
# Run Dev
# ----------------------------------------------------
FROM app-run AS dev
ENV ENV="dev" \
ARKLET_DEBUG="True"

CMD ["bash"]

# ----------------------------------------------------
# Run Prod
# ----------------------------------------------------
FROM app-run AS prod
ENV ENV="prod" \
ARKLET_DEBUG="False"

CMD ["./entrypoint.sh"]

# # For some _real_ performance, at cost of ease of use:
# FROM python:3.9-alpine as prod
# ENV PATH="/opt/venv/bin:$PATH"
# COPY . .
# RUN apk add python3-dev build-base linux-headers pcre-dev
# RUN pip install uwsgi
79 changes: 74 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,19 @@ See https://arks.org/
## What is Arklet?
Arklet is a Python Django application for minting, binding, and resolving ARKs. It is intended to follow best practices set out by https://arks.org/.

## Running Locally with Postgres
## Running

Create the default config YAML file: **/etc/arklet.yml**
### Locally with Postgres

- Set a value for **ARKLET_DJANGO_SECRET_KEY**.
- For local development, set **ARKLET_DEBUG** to True.
Create the default `.env` file in the project's root directory

```
# /!\ Set your own secret key /!\
ARKLET_DJANGO_SECRET_KEY=[YOUR_SECRET]

# For local development, set to True
ARKLET_DEBUG=True
```

Run Postgres, install into virtual environment, and start the app:
```
Expand All @@ -29,11 +36,73 @@ python manage.py createsuperuser
python manage.py runserver
```

### Separate dockers
Using docker, we can use a [this provided](./docker/env.docker.local) config file.

See above for running PostgreSQL, and run the **Arklet** docker as follows (in *bash*):
```
docker build \
--target dev \
-t "arklet" -f ./Dockerfile . \
--build-arg ENV=DEV \
&& docker run --rm -it \
-p 8000:8000 \
--env-file=./docker/env.docker.local \
-e ARKLETDEBUG="true" \
--name arklet \
-v `pwd`/ark:/app/ark \
-v `pwd`/ark_import:/app/ark_import \
-v `pwd`/arklet:/app/arklet \
arklet
```

### With docker-compose
Using the provided `docker-compose.yml` with default settings in the [docker
configuration directory](./docker) :

```
docker-compose up
```

By default, the folders `ark`, `ark_import` and `arklet` are mounted in the
container. Should you wish to attach a console to the `arklet` container (needed
to create the django superuser) :
```
# In another shell
docker exec -it arklet_django /bin/bash
# You're now in the docker container
./manage.py createsuperuser
```

### First steps
Create your first NAAN, Key, and Shoulder in the admin:
127.0.0.1:8000/admin

And by the way, you now host a working ARK resolver! You can already
try the following ones :
- [http://127.0.0.1:8000/ark:/13960/t5n960f7n](http://127.0.0.1:8000/ark:/13960/t5n960f7n)
- [http://127.0.0.1:8000/ark:/67375/C0X-SPWFRSGR-N](http://127.0.0.1:8000/ark:/67375/C0X-SPWFRSGR-N)
- [http://127.0.0.1:8000/ark:/12148/bpt6k65358454](http://127.0.0.1:8000/ark:/12148/bpt6k65358454)
- ...

Happy minting, binding, and resolving!

## Configuration Options

See arklet/settings.py for the full list of options to put in your config file.
See arklet/settings.py for the full list of options to put in your config file.

## Deploying
### With docker
Using the provided Dockerfile (is you wish to set a build target, use `prod`,
but being the default target you can skip this), provide the following values
in your environment:

- ARKLET_DJANGO_SECRET_KEY=[YOUR_SECRET]
- ARKLET_DEBUG=False
- ARKLET_HOST=0.0.0.0
- ARKLET_PORT=[Port of choice]
- ARKLET_POSTGRES_NAME=[DB NAME]
- ARKLET_POSTGRES_USER=[DB USER]
- ARKLET_POSTGRES_PASSWORD=[DB PASS]
- ARKLET_POSTGRES_HOST=[DB HOST]
- ARKLET_POSTGRES_PORT=[DB PORT]
11 changes: 5 additions & 6 deletions ark/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,9 @@ def resolve_ark(request, ark: str):
return HttpResponseRedirect(ark.url)
except Ark.DoesNotExist:
try:
naan = Naan.objects.get(naan=naan)
resolver = naan.url or "https://n2t.net"
# TODO: more robust resolver URL creation
return HttpResponseRedirect(f"{resolver}/ark:/{naan.naan}/{assigned_name}")
naan_obj = Naan.objects.get(naan=naan)
return HttpResponseRedirect(f"{naan_obj.url}/ark:/{naan_obj.naan}/{assigned_name}")
except Naan.DoesNotExist:
# TODO: make nice page saying we don't know about the ARK or NAAN
raise Http404
resolver = "https://n2t.net"
# TODO: more robust resolver URL creation
return HttpResponseRedirect(f"{resolver}/ark:/{naan}/{assigned_name}")
45 changes: 28 additions & 17 deletions arklet/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,22 @@
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
import yaml


with open(os.environ.get("ARKLET_CONF", "/etc/arklet.yml")) as f:
conf = yaml.safe_load(f)

import environ

env = environ.Env(
# set default values
ARKLET_HOST=(str, "127.0.0.1"),
ARKLET_DEBUG=(bool, False),
ARKLET_POSTGRES_NAME=(str, "arklet"),
ARKLET_POSTGRES_HOST=(str, "127.0.0.1"),
ARKLET_POSTGRES_PORT=(str, "5432"),
ARKLET_POSTGRES_USER=(str, "arklet"),
ARKLET_POSTGRES_PASSWORD=(str, "arklet"),
ARKLET_SENTRY_DSN=(str, ""),
ARKLET_SENTRY_TRANSACTIONS_PER_TRACE=(int, 1),
ARKLET_STATIC_ROOT=(str, "static"),
ARKLET_MEDIA_ROOT=(str, "media"),
)
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

Expand All @@ -29,13 +40,13 @@
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = conf["ARKLET_DJANGO_SECRET_KEY"] # Intentionally no default value
SECRET_KEY = env.str("ARKLET_DJANGO_SECRET_KEY") # Intentionally no default value

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = conf.get("ARKLET_DEBUG", False)
DEBUG = env("ARKLET_DEBUG")

ALLOWED_HOSTS = [
conf.get("ARKLET_HOST", "127.0.0.1"),
env("ARKLET_HOST"),
"wbgrp-svc302.us.archive.org",
"qa-ark.archive.org",
"ark.archive.org",
Expand Down Expand Up @@ -107,11 +118,11 @@
# "ENGINE": "django.db.backends.sqlite3",
# "NAME": BASE_DIR / "db.sqlite3",
"ENGINE": "django.db.backends.postgresql",
"NAME": conf.get("ARKLET_POSTGRES_NAME", "arklet"),
"HOST": conf.get("ARKLET_POSTGRES_HOST", "127.0.0.1"),
"PORT": conf.get("ARKLET_POSTGRES_PORT", "5432"),
"USER": conf.get("ARKLET_POSTGRES_USER", "arklet"),
"PASSWORD": conf.get("ARKLET_POSTGRES_PASSWORD", "arklet"),
"NAME": env("ARKLET_POSTGRES_NAME"),
"HOST": env("ARKLET_POSTGRES_HOST"),
"PORT": env("ARKLET_POSTGRES_PORT"),
"USER": env("ARKLET_POSTGRES_USER"),
"PASSWORD": env("ARKLET_POSTGRES_PASSWORD"),
"DISABLE_SERVER_SIDE_CURSORS": True,
}
}
Expand Down Expand Up @@ -158,17 +169,17 @@

STATIC_URL = "/static/"

STATIC_ROOT = conf.get("ARKLET_STATIC_ROOT")
STATIC_ROOT = env.str("ARKLET_STATIC_ROOT")

MEDIA_ROOT = conf.get("ARKLET_MEDIA_ROOT")
MEDIA_ROOT = env.str("ARKLET_MEDIA_ROOT")

# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

SENTRY_DSN = conf.get("ARKLET_SENTRY_DSN", "")
SENTRY_SAMPLE_RATE = 1 / int(conf.get("ARKLET_SENTRY_TRANSACTIONS_PER_TRACE", 1))
SENTRY_DSN = env("ARKLET_SENTRY_DSN")
SENTRY_SAMPLE_RATE = 1 / int(env("ARKLET_SENTRY_TRANSACTIONS_PER_TRACE"))
sentry_sdk.init(
dsn=SENTRY_DSN,
integrations=[DjangoIntegration()],
Expand Down
35 changes: 35 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
version: "3.7"

services:
postgres:
container_name: arkled_db
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be arklet_db

image: postgres:14-alpine
env_file:
- ./docker/env.docker.local
volumes:
- postgres:/var/lib/postgresql/data
restart: always
ports:
- "5432:5432"

arklet:
container_name: arklet_django
restart: always
build:
context: .
target: dev
dockerfile: ./Dockerfile
command: /app/entrypoint.sh
volumes:
- ./ark:/app/ark
- ./ark_import:/app/ark_import
- ./arklet:/app/arklet
env_file:
- ./docker/env.docker.local
ports:
- "8000:8000"
depends_on:
- postgres

volumes:
postgres:
Loading