├── requirements.txt ├── README.md ├── Dockerfile ├── reset_app.sh ├── test_hello_world.py ├── hello_world.py ├── deploy_app.sh ├── .circleci └── config.yml └── .gitignore /requirements.txt: -------------------------------------------------------------------------------- 1 | flask 2 | pyinstaller -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python-circleci-docker 2 | 3 | Demonstrates how to build, test & deploy a flask python app to Docker Hub then run a Docker container on a server via SSH 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:2.7.14 2 | 3 | RUN mkdir /opt/hello_word/ 4 | WORKDIR /opt/hello_word/ 5 | 6 | COPY requirements.txt . 7 | COPY dist/hello_world /opt/hello_word/ 8 | 9 | EXPOSE 80 10 | 11 | CMD [ "./hello_world" ] 12 | -------------------------------------------------------------------------------- /reset_app.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ############################ 4 | # Reset the hellow_world app on a server 5 | # 6 | # Use: ssh -o StrictHostKeyChecking=no root@hello.dpunks.org "./reset_app.sh" replace with our server hostname 7 | # 8 | ############################# 9 | 10 | echo "Removing Hello World to Docker Container" 11 | docker stop hello_world 12 | echo "Stoping Hello World in Docker Container" 13 | docker ps -a -------------------------------------------------------------------------------- /test_hello_world.py: -------------------------------------------------------------------------------- 1 | import hello_world 2 | import unittest 3 | 4 | class TestHelloWorld(unittest.TestCase): 5 | 6 | def setUp(self): 7 | self.app = hello_world.app.test_client() 8 | self.app.testing = True 9 | 10 | def test_status_code(self): 11 | response = self.app.get('/') 12 | self.assertEqual(response.status_code, 200) 13 | 14 | def test_message(self): 15 | response = self.app.get('/') 16 | message = hello_world.wrap_html('Hello PyLadies Chicago!') 17 | self.assertEqual(response.data, message) 18 | 19 | if __name__ == '__main__': 20 | unittest.main() -------------------------------------------------------------------------------- /hello_world.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | 3 | app = Flask(__name__) 4 | 5 | def wrap_html(message): 6 | html = """ 7 | 8 | 9 |
10 |
11 | 12 |
13 | {0}
14 |
15 |
16 | 17 | """.format(message) 18 | return html 19 | 20 | @app.route('/') 21 | def hello_world(): 22 | message = 'Hello PyLadies Chicago!' 23 | html = wrap_html(message) 24 | return html 25 | 26 | if __name__ == '__main__': 27 | app.run(host='0.0.0.0', port=5000) -------------------------------------------------------------------------------- /deploy_app.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ######################################## 3 | # Put this on a Server 4 | # run chmod +x deploy_app.sh to make the script executable 5 | # 6 | # Execute this script: ./deploy_app.sh ariv3ra/python-circleci-docker:$TAG 7 | # Replace the $TAG with the actual Build Tag you want to deploy 8 | # 9 | ######################################## 10 | 11 | set -e 12 | 13 | DOCKER_IMAGE=$1 14 | CONAINER_NAME="hello_world" 15 | 16 | # Check for arguments 17 | if [[ $# -lt 1 ]] ; then 18 | echo '[ERROR] You must supply a Docker Image to pull' 19 | exit 1 20 | fi 21 | 22 | echo "Deploying Hello World to Docker Container" 23 | 24 | #Check for running container & stop it before starting a new one 25 | if [ $(docker inspect -f '{{.State.Running}}' $CONAINER_NAME) = "true" ]; then 26 | docker stop hello_world 27 | fi 28 | 29 | echo "Starting Hello World using Docker Image name: $DOCKER_IMAGE" 30 | 31 | docker run -d --rm=true -p 80:5000 --name hello_world $DOCKER_IMAGE 32 | 33 | docker ps -a 34 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/python:2.7.14 6 | environment: 7 | FLASK_CONFIG: testing 8 | steps: 9 | - checkout 10 | - add_ssh_keys: 11 | fingerprints: 12 | - 62:b3:b1:c1:7d:56:af:06:db:9b:26:70:98:e2:da:23 13 | - run: 14 | name: Setup VirtualEnv 15 | command: | 16 | echo 'export TAG=0.1.${CIRCLE_BUILD_NUM}' >> $BASH_ENV 17 | echo 'export IMAGE_NAME=python-circleci-docker' >> $BASH_ENV 18 | virtualenv helloworld 19 | . helloworld/bin/activate 20 | pip install --no-cache-dir -r requirements.txt 21 | - run: 22 | name: Run Tests 23 | command: | 24 | . helloworld/bin/activate 25 | python test_hello_world.py 26 | - setup_remote_docker: 27 | docker_layer_caching: true 28 | - run: 29 | name: Build and push Docker image 30 | command: | 31 | . helloworld/bin/activate 32 | pyinstaller -F hello_world.py 33 | docker build -t $DOCKER_LOGIN/$IMAGE_NAME:$TAG . 34 | echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin 35 | docker push $DOCKER_LOGIN/$IMAGE_NAME:$TAG 36 | - run: 37 | name: Deploy app to Linode Producrion Server via Docker 38 | command: | 39 | ssh -o StrictHostKeyChecking=no root@fosscon.punkdata.org "/bin/bash ./deploy_app.sh $DOCKER_LOGIN/$IMAGE_NAME:$TAG" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | .vscode/ 107 | .DS_Store --------------------------------------------------------------------------------