I have a basic REST API implemented in Flask. I want to try using Docker to containerize it. I'm completely new to Docker, but based on what I was able to figure out on various forums, this is what I have set up.
Dockerfile
FROM python:3.11
WORKDIR /app
COPY ./requirements.txt /app/requirements.txt
COPY .env /app/.env
COPY . /app
RUN python3 -m pip install -r /app/requirements.txt
EXPOSE 5000
ENTRYPOINT ["python3"]
CMD ["app.py", "--host=0.0.0.0"]
requirements.txt
aiohttp==3.8.6
aiohttp-retry==2.8.3
aiosignal==1.3.1
async-timeout==4.0.3
attrs==23.1.0
blinker==1.6.3
certifi==2023.7.22
charset-normalizer==3.3.1
click==8.1.7
distlib==0.3.7
filelock==3.12.4
Flask==2.3.0
Flask-Cors==4.0.0
flask-marshmallow==0.14.0
Flask-MySQL==1.5.2
Flask-MySQLdb==2.0.0
Flask-SQLAlchemy==3.1.1
frozenlist==1.4.0
idna==3.4
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.3
marshmallow-sqlalchemy==0.29.0
multidict==6.0.4
mysqlclient==2.2.0
packaging==23.2
platformdirs==3.11.0
PyJWT==2.8.0
PyMySQL==1.1.0
python-dotenv==1.0.0
requests==2.31.0
six==1.16.0
SQLAlchemy==2.0.22
twilio==8.10.0
typing_extensions==4.8.0
urllib3==2.0.7
virtualenv==20.24.5
Werkzeug==3.0.0
yarl==1.9.2
app.py
from flask import Flask, request, jsonify, json
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.event import listens_for
from flaskext.mysql import MySQL
from flask_cors import CORS
from dataclasses import dataclass
from sqlalchemy import text
from urllib.parse import quote
app = Flask(__name__)
CORS(app, origins=["http://localhost:3000", "http://localhost:3000"])
db = SQLAlchemy()
mysql =MySQL()
@dataclass
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
firstname = db.Column(db.String(46), nullable=False)#1
lastname = db.Column(db.String(46), nullable=False)#1
def __init__(self, firstname, lastname):
self.firstname = firstname
self.lastname = lastname
def as_dict(self):
excluded_fields = ['id']
return {field.name:getattr(self, field.name) for field in self.__table__.c if field.name not in excluded_fields}
@dataclass
class User(db.Model):
__tablename__ = 'user'
__table_args__ = {'extend_existing': True}
id = db.Column(db.Integer, primary_key=True)
firstname = db.Column(db.String(46), nullable=False)#1
lastname = db.Column(db.String(46), nullable=False)#1
def __init__(self, firstname, lastname):
self.firstname = firstname
self.lastname = lastname
def as_dict(self):
excluded_fields = ['id']
return {field.name:getattr(self, field.name) for field in self.__table__.c if field.name not in excluded_fields}
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://username:pwd@127.0.0.1/test'
db.init_app(app)
with app.app_context():
db.create_all()
@app.route('/users', methods=['GET'])
def get_user():
users = User.query.all()
return jsonify(users)
@app.route('/user/<firstname>', methods=['GET'])
def user_byfirstname(firstname):
user = User.query.filter_by(firstname = firstname).first()
return jsonify(user.as_dict())
if __name__ == '__main__':
app.run(debug=True)
App tree:
I then go to the Terminal, and run $ docker build -t myapp:latest
.
The build is successful, and I can see my app listed in the Docker Desktop app
I then run
$ docker run --rm -it -p 8080:5000 myapp:latest
* Running on http://127.0.0.1:5000
Press CTRL+C to quit
* Restarting with stat
/usr/local/lib/python3.11/site-packages/flask_sqlalchemy/model.py:144: SAWarning: This declarative base already contains a class with the same class name and module name as __main__.User, and will be replaced in the string-lookup table.
super().__init__(name, bases, d, **kwargs)
* Debugger is active!
* Debugger PIN: 581-248-767
Docker Desktop shows myapp is in use.
So far, so good. But this is where I start running into issues.
From host machine (through Postman) I cannot access the app on port 8080 with:
127.0.0.1:8080/users
Postman throws the error : Error: read ECONNRESET
I really don't know what to do or where to go from here, every source I've tried has given me a slight variation on what I have already attempted, and I'm no closer to getting this to work. Please help, thanks.
The output of the Flask server points out that its only running on the containers local network. (127.0.0.1) and not on all interfaces.
That is because you are running the flask development server with python3 app.py --host=0.0.0.0
. When you run it like that, Flask run with the default values and with the values given through the function app.run()
.
The recommended way is to use the flask cli command to run your app so you can configure flask externally and not hard code variables or modify your app.run()
with the variables you need.
The recommended way would be to change your dockerfile and use flask cli like this:
FROM python:3.11-alpine
# Virtual envs not needed inside container
WORKDIR /app
COPY ./requirements.txt /app/requirements.txt
COPY . /app
RUN python3 -m pip install -r /app/requirements.txt
EXPOSE 5000
CMD ["flask", "--app=app", "run", "--host=0.0.0.0"]
The other option is modifying your app.run()
and add the host parameter:
if __name__ == '__main__':
app.run(debug=True, host="0.0.0.0")