CARVIEW |
Navigation Menu
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Advanced Deployment
NOTICE: This wiki is deprecated! Please visit the new documentation site!
There are a variety of ways to deploy CTFd using WSGI compliant servers. The Flask documentation tries not to lean towards any software setup, but there are some pitfalls to consider due to the design decisions of both Flask and these servers. The deployment configuration you choose will typically affect how many users can comfortably access your CTF.
Flask deployment options are a little confusing so this page will break some of them down into simple instructions and cover the benefits or drawbacks of each option.
Also, we recommended using multiple worker/processes for whichever method you choose, since a single worker is rarely enough for more than a dozen users.
CTFd automatically generates a secret session for itself named .ctfd_secret_key
. This key is by default not committed into git by .gitignore
. It is important that if for some reason you start to deploy CTFd on multiple servers that you either override this default value with a hardcoded value or you commit the .ctfd_secret_key
into your source repository.
To generate a secret key to replace in config.py you can run the following command:
python -c "import os; print repr(os.urandom(32))"
In CTFd/config.py the SQLALCHEMY_DATABASE_URI setting should be modified to support a database that is more performant than sqlite. My personal suggestion is MySQL or MariaDB, but PostgreSQL will work just fine. The associated documentation from SQLAlchemy is here and the database URI follows the following format:
dialect+driver://username:password@host:port/database
For MySQL the following example can be modified and used:
mysql+pymysql://root:<YOUR_PASSWORD_HERE>@localhost/ctfd
If CTFd is capable (due to permissions), it will create the database for you and create the tables that it requires. You can choose to create the database and then give CTFd the empty db and CTFd will create the tables that it needs to operate.
Note: Due to development constraints the officially supported database is MySQL/MariaDB although development efforts will focus on feature parity between SQLite, MySQL/MariaDB, and Postgres.
CTFd makes use of Flask-Caching to implement its caching logic. In config.py you can set the caching server with the following syntax:
redis://user:password@localhost:6379
Once you have appropriately configured the secret key, database, caching server, and any other configuration values, you are ready to deploy CTFd using one of the following options.
The absolute simplest way to deploy CTFd merely involves running python serve.py
to start Flask's built-in debugging server. This isn't recommended for anything but debugging or a CTF with under 50 users (classroom environments). To turn off debug mode and customize the port number, serve.py should be modified.
If you are interested in serving CTFd under a subdirectory you can do so with the following script. Bear in mind that this still uses the development server and shouldn't be used for production. Continue to the uwsgi section to deploy CTFd on a subdirectory for production:
from flask import Flask
from CTFd import create_app
app = create_app()
# Relevant documents:
# https://werkzeug.pocoo.org/docs/middlewares/
# https://flask.pocoo.org/docs/patterns/appdispatch/
from werkzeug.serving import run_simple
from werkzeug.wsgi import DispatcherMiddleware
app.config['DEBUG'] = True
# Configure this to whatever subdirectory you wish.
# You may want to put it in config.py as well.
app.config['APPLICATION_ROOT'] = '/ctfd'
# Load a dummy app at the root URL to give 404 errors.
# Serve app at APPLICATION_ROOT for localhost development.
application = DispatcherMiddleware(Flask('dummy_app'), {
app.config['APPLICATION_ROOT']: app,
})
run_simple('localhost', 4000, application, use_reloader=True)
You can use also docker-compose and docker to build a CTFd container. However, this is only recommended for advanced users.
In order to use docker-compose you will need a recent version of Docker
pip install docker-compose
git clone https://github.com/isislab/CTFd.git
cd CTFd
docker-compose up
Note: If you run into errors, try docker-compose build --no-cache
and then docker-compose up
gunicorn is most likely the easiest yet performant method of deploying CTFd. It is used by the associated Docker entrypoint file in the repo. Currently ctfd.io uses nginx and gunicorn to host CTFd instances.
- Install gunicorn from pip:
pip install gunicorn
- Run
gunicorn --bind 0.0.0.0:8000 -w 4 "CTFd:create_app()"
This will run gunicorn in the foreground. To configure gunicorn to run in the background it's recommended to use an Upstart or a systemd script.
While gunicorn and uwsgi are similar, gunicorn is far easier to configure and is arguably all most CTFs need. In addition, some uwsgi users have reported issues relating to SQLAlchemy while using uwsgi. gunicorn
is the suggested server for deploying CTFd.
uwsgi is probably one of the more confusing ways of deploying CTFd, but it has native integration with nginx and can be used with other web servers with a reverse proxy.
- Run
pip install uwsgi
- Run
uwsgi --socket 127.0.0.1:8000 --protocol=http -w "CTFd:create_app()"
.
Using uwsgi
it's possible to deploy CTFd on a "subdirectory" using the following command:
uwsgi --socket 127.0.0.1:8000 --protocol=http --mount '/ctf'="CTFd:create_app()" --manage-script-name
CTFd would then be available on https://127.0.0.1:8000/ctf. This can be useful if you want to host a standard website on the main index page but still host a CTF on some subdirectory. Potentially useful for conference or workshop websites.
With uwsgi running you can use something similar to the following nginx configuration to point to CTFd running inside uwsgi.
location /ctf/ {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Script-Name /ctf;
proxy_pass https://127.0.0.1:8000;
}
Note: Some individuals have reported that the SQLAlchemy Session in CTFd is not thread safe, meaning that you should probably run uwsgi
with --threads 1
Note: uwsgi does not use the standard logging mechanism most Flask devs will be used to, so things like print statements will not show up in the server console. Instead of a normal print statement, use print >> sys.stderr, msg
to print to the console. You can also use the --tolog switch to force logging to a file.
The Flask documentation covers this option very well. The only exception will be that the wsgi file will look something like:
import sys
sys.path.insert(0, '/path/to/the/application')
from CTFd import create_app
application = create_app()
The downside of Apache and mod_wsgi is that Apache tends to not run well on low resource web servers.
Again the Flask documentation covers this option very well. The only difference is that the uwsgi command will look different, run the following in the directory of serve.py:
uwsgi -s /tmp/uwsgi.sock -w 'CTFd:create_app()'
For most situations I would use the nginx configuration discussed that binds CTFd to the URL root.
I've found that nginx and uwsgi keep resource usage lower. But setting up uwsgi seemed rather confusing to me coming from mostly setting up Apache web servers.