Skip to content

Commit

Permalink
Merge pull request #3 from tiangolo/add-prestart-sh
Browse files Browse the repository at this point in the history
Add support for /app/prestart.sh
  • Loading branch information
tiangolo authored Feb 8, 2019
2 parents e9ff167 + f509de0 commit f8deee3
Show file tree
Hide file tree
Showing 37 changed files with 582 additions and 130 deletions.
40 changes: 37 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,16 +294,50 @@ It uses the environment variables declared above to set all the configurations.

You can override it by including a file in:

* `/app/gunicorn_conf.py`,
* `/app/app/gunicorn_conf.py` or replacing the one in
* `/gunicorn_conf.py` or replacing the one in
* `/app/gunicorn_conf.py`
* `/app/app/gunicorn_conf.py`
* `/gunicorn_conf.py`


### Custom `/app/prestart.sh`

If you need to run anything before starting the app, you can add a file `prestart.sh` to the directory `/app`. The image will automatically detect and run it before starting everything.

For example, if you want to add Alembic SQL migrations (with SQLALchemy), you could create a `./app/prestart.sh` file in your code directory (that will be copied by your `Dockerfile`) with:

```bash
#! /usr/bin/env bash

# Let the DB start
sleep 10;
# Run migrations
alembic upgrade head
```

and it would wait 10 seconds to give the database some time to start and then run that `alembic` command.

If you need to run a Python script before starting the app, you could make the `/app/prestart.sh` file run your Python script, with something like:

```bash
#! /usr/bin/env bash

# Run custom Python script before starting
python /app/my_custom_prestart_script.py
```


## Tests

All the image tags, configurations, environment variables and application options are tested.


## Release Notes

### 0.1.0

* Add support for `/app/prestart.sh`.


## License

This project is licensed under the terms of the MIT license.
7 changes: 7 additions & 0 deletions python3.6-alpine3.8/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ RUN apk add --no-cache --virtual .build-deps gcc libc-dev \
COPY ./entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

COPY ./start.sh /start.sh
RUN chmod +x /start.sh

COPY ./gunicorn_conf.py /gunicorn_conf.py

COPY ./app /app
Expand All @@ -19,3 +22,7 @@ ENV PYTHONPATH=/app
EXPOSE 80

ENTRYPOINT ["/entrypoint.sh"]

# Run the start script, it will check for an /app/prestart.sh script (e.g. for migrations)
# And then will start Gunicorn with Meinheld
CMD ["/start.sh"]
12 changes: 12 additions & 0 deletions python3.6-alpine3.8/app/prestart.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#! /usr/bin/env sh

echo "Running inside /app/prestart.sh, you could add migrations to this file, e.g.:"

echo "
#! /usr/bin/env bash
# Let the DB start
sleep 10;
# Run migrations
alembic upgrade head
"
6 changes: 1 addition & 5 deletions python3.6-alpine3.8/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,4 @@ else
fi
export GUNICORN_CONF=${GUNICORN_CONF:-$DEFAULT_GUNICORN_CONF}

if [ -z "$@" ]; then
gunicorn -k egg:meinheld#gunicorn_worker -c "$GUNICORN_CONF" "$APP_MODULE"
else
exec "$@"
fi
exec "$@"
15 changes: 15 additions & 0 deletions python3.6-alpine3.8/start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#! /usr/bin/env sh
set -e

# If there's a prestart.sh script in the /app directory, run it before starting
PRE_START_PATH=/app/prestart.sh
echo "Checking for script in $PRE_START_PATH"
if [ -f $PRE_START_PATH ] ; then
echo "Running script $PRE_START_PATH"
. "$PRE_START_PATH"
else
echo "There is no script $PRE_START_PATH"
fi

# Start Gunicorn
exec gunicorn -k egg:meinheld#gunicorn_worker -c "$GUNICORN_CONF" "$APP_MODULE"
7 changes: 7 additions & 0 deletions python3.6/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ RUN pip install meinheld gunicorn
COPY ./entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

COPY ./start.sh /start.sh
RUN chmod +x /start.sh

COPY ./gunicorn_conf.py /gunicorn_conf.py

COPY ./app /app
Expand All @@ -17,3 +20,7 @@ ENV PYTHONPATH=/app
EXPOSE 80

ENTRYPOINT ["/entrypoint.sh"]

# Run the start script, it will check for an /app/prestart.sh script (e.g. for migrations)
# And then will start Gunicorn with Meinheld
CMD ["/start.sh"]
12 changes: 12 additions & 0 deletions python3.6/app/prestart.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#! /usr/bin/env sh

echo "Running inside /app/prestart.sh, you could add migrations to this file, e.g.:"

echo "
#! /usr/bin/env bash
# Let the DB start
sleep 10;
# Run migrations
alembic upgrade head
"
6 changes: 1 addition & 5 deletions python3.6/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,4 @@ else
fi
export GUNICORN_CONF=${GUNICORN_CONF:-$DEFAULT_GUNICORN_CONF}

if [ -z "$@" ]; then
gunicorn -k egg:meinheld#gunicorn_worker -c "$GUNICORN_CONF" "$APP_MODULE"
else
exec "$@"
fi
exec "$@"
15 changes: 15 additions & 0 deletions python3.6/start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#! /usr/bin/env sh
set -e

# If there's a prestart.sh script in the /app directory, run it before starting
PRE_START_PATH=/app/prestart.sh
echo "Checking for script in $PRE_START_PATH"
if [ -f $PRE_START_PATH ] ; then
echo "Running script $PRE_START_PATH"
. "$PRE_START_PATH"
else
echo "There is no script $PRE_START_PATH"
fi

# Start Gunicorn
exec gunicorn -k egg:meinheld#gunicorn_worker -c "$GUNICORN_CONF" "$APP_MODULE"
7 changes: 7 additions & 0 deletions python3.7-alpine3.8/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ RUN apk add --no-cache --virtual .build-deps gcc libc-dev \
COPY ./entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

COPY ./start.sh /start.sh
RUN chmod +x /start.sh

COPY ./gunicorn_conf.py /gunicorn_conf.py

COPY ./app /app
Expand All @@ -19,3 +22,7 @@ ENV PYTHONPATH=/app
EXPOSE 80

ENTRYPOINT ["/entrypoint.sh"]

# Run the start script, it will check for an /app/prestart.sh script (e.g. for migrations)
# And then will start Gunicorn with Meinheld
CMD ["/start.sh"]
12 changes: 12 additions & 0 deletions python3.7-alpine3.8/app/prestart.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#! /usr/bin/env sh

echo "Running inside /app/prestart.sh, you could add migrations to this file, e.g.:"

echo "
#! /usr/bin/env bash
# Let the DB start
sleep 10;
# Run migrations
alembic upgrade head
"
6 changes: 1 addition & 5 deletions python3.7-alpine3.8/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,4 @@ else
fi
export GUNICORN_CONF=${GUNICORN_CONF:-$DEFAULT_GUNICORN_CONF}

if [ -z "$@" ]; then
gunicorn -k egg:meinheld#gunicorn_worker -c "$GUNICORN_CONF" "$APP_MODULE"
else
exec "$@"
fi
exec "$@"
15 changes: 15 additions & 0 deletions python3.7-alpine3.8/start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#! /usr/bin/env sh
set -e

# If there's a prestart.sh script in the /app directory, run it before starting
PRE_START_PATH=/app/prestart.sh
echo "Checking for script in $PRE_START_PATH"
if [ -f $PRE_START_PATH ] ; then
echo "Running script $PRE_START_PATH"
. "$PRE_START_PATH"
else
echo "There is no script $PRE_START_PATH"
fi

# Start Gunicorn
exec gunicorn -k egg:meinheld#gunicorn_worker -c "$GUNICORN_CONF" "$APP_MODULE"
7 changes: 7 additions & 0 deletions python3.7/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ RUN pip install meinheld gunicorn
COPY ./entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

COPY ./start.sh /start.sh
RUN chmod +x /start.sh

COPY ./gunicorn_conf.py /gunicorn_conf.py

COPY ./app /app
Expand All @@ -17,3 +20,7 @@ ENV PYTHONPATH=/app
EXPOSE 80

ENTRYPOINT ["/entrypoint.sh"]

# Run the start script, it will check for an /app/prestart.sh script (e.g. for migrations)
# And then will start Gunicorn with Meinheld
CMD ["/start.sh"]
12 changes: 12 additions & 0 deletions python3.7/app/prestart.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#! /usr/bin/env sh

echo "Running inside /app/prestart.sh, you could add migrations to this file, e.g.:"

echo "
#! /usr/bin/env bash
# Let the DB start
sleep 10;
# Run migrations
alembic upgrade head
"
6 changes: 1 addition & 5 deletions python3.7/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,4 @@ else
fi
export GUNICORN_CONF=${GUNICORN_CONF:-$DEFAULT_GUNICORN_CONF}

if [ -z "$@" ]; then
gunicorn -k egg:meinheld#gunicorn_worker -c "$GUNICORN_CONF" "$APP_MODULE"
else
exec "$@"
fi
exec "$@"
15 changes: 15 additions & 0 deletions python3.7/start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#! /usr/bin/env sh
set -e

# If there's a prestart.sh script in the /app directory, run it before starting
PRE_START_PATH=/app/prestart.sh
echo "Checking for script in $PRE_START_PATH"
if [ -f $PRE_START_PATH ] ; then
echo "Running script $PRE_START_PATH"
. "$PRE_START_PATH"
else
echo "There is no script $PRE_START_PATH"
fi

# Start Gunicorn
exec gunicorn -k egg:meinheld#gunicorn_worker -c "$GUNICORN_CONF" "$APP_MODULE"
37 changes: 26 additions & 11 deletions tests/test_01_main/test_defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,29 @@
import pytest
import requests

from ..utils import CONTAINER_NAME, get_config, stop_previous_container
from ..utils import CONTAINER_NAME, get_config, get_logs, remove_previous_container

client = docker.from_env()


def verify_container(container, response_text):
config_data = get_config(container)
assert config_data["workers_per_core"] == 2
assert config_data["host"] == "0.0.0.0"
assert config_data["port"] == "80"
assert config_data["loglevel"] == "info"
assert config_data["workers"] > 2
assert config_data["bind"] == "0.0.0.0:80"
logs = get_logs(container)
assert "Checking for script in /app/prestart.sh" in logs
assert "Running script /app/prestart.sh" in logs
assert (
"Running inside /app/prestart.sh, you could add migrations to this file" in logs
)
response = requests.get("http://127.0.0.1:8000")
assert response.text == response_text


@pytest.mark.parametrize(
"image,response_text",
[
Expand All @@ -35,19 +53,16 @@
],
)
def test_defaults(image, response_text):
stop_previous_container(client)
remove_previous_container(client)
container = client.containers.run(
image, name=CONTAINER_NAME, ports={"80": "8000"}, detach=True
)
config_data = get_config(container)
assert config_data["workers_per_core"] == 2
assert config_data["host"] == "0.0.0.0"
assert config_data["port"] == "80"
assert config_data["loglevel"] == "info"
assert config_data["workers"] > 2
assert config_data["bind"] == "0.0.0.0:80"
time.sleep(1)
response = requests.get("http://127.0.0.1:8000")
assert response.text == response_text
verify_container(container, response_text)
container.stop()
# Test that everything works after restarting too
container.start()
time.sleep(1)
verify_container(container, response_text)
container.stop()
container.remove()
35 changes: 25 additions & 10 deletions tests/test_01_main/test_env_vars_1.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,28 @@
import pytest
import requests

from ..utils import CONTAINER_NAME, get_config, stop_previous_container
from ..utils import CONTAINER_NAME, get_config, get_logs, remove_previous_container

client = docker.from_env()


def verify_container(container, response_text):
config_data = get_config(container)
assert config_data["workers_per_core"] == 1
assert config_data["host"] == "0.0.0.0"
assert config_data["port"] == "8000"
assert config_data["loglevel"] == "warning"
assert config_data["bind"] == "0.0.0.0:8000"
logs = get_logs(container)
assert "Checking for script in /app/prestart.sh" in logs
assert "Running script /app/prestart.sh" in logs
assert (
"Running inside /app/prestart.sh, you could add migrations to this file" in logs
)
response = requests.get("http://127.0.0.1:8000")
assert response.text == response_text


@pytest.mark.parametrize(
"image,response_text",
[
Expand All @@ -35,22 +52,20 @@
],
)
def test_env_vars_1(image, response_text):
stop_previous_container(client)
remove_previous_container(client)
container = client.containers.run(
image,
name=CONTAINER_NAME,
environment={"WORKERS_PER_CORE": 1, "PORT": "8000", "LOG_LEVEL": "warning"},
ports={"8000": "8000"},
detach=True,
)
config_data = get_config(container)
assert config_data["workers_per_core"] == 1
assert config_data["host"] == "0.0.0.0"
assert config_data["port"] == "8000"
assert config_data["loglevel"] == "warning"
assert config_data["bind"] == "0.0.0.0:8000"
time.sleep(1)
response = requests.get("http://127.0.0.1:8000")
assert response.text == response_text
verify_container(container, response_text)
container.stop()
# Test that everything works after restarting too
container.start()
time.sleep(1)
verify_container(container, response_text)
container.stop()
container.remove()
Loading

0 comments on commit f8deee3

Please sign in to comment.