One place for hosting & domains

      October 2019

      How to Build a Django and Gunicorn Application with Docker


      Introduction

      Django is a powerful web framework that can help you get your Python application off the ground quickly. It includes several convenient features like an object-relational mapper, user authentication, and a customizable administrative interface for your application. It also includes a caching framework and encourages clean app design through its URL Dispatcher and Template system.

      In this tutorial, you’ll learn how to build a scalable and portable Django Polls app with Docker containers. Out of the box, a Django app requires several modifications to run effectively inside of containers, like logging to standard output streams and configuring itself through environment variables passed into the container. In addition, offloading static assets like JavaScript and CSS stylesheets to object storage allows you to streamline and centralize the management of these files in a multi-container environment.

      You’ll implement these modifications—inspired by the Twelve-Factor methodology for building scalable, cloud-native web apps—on a sample Django Polls app. Then, you’ll build the application image and run the containerized app with Docker.

      By the end of this tutorial, you’ll have containerized the setup in How to Set Up a Scalable Django App. In subsequent tutorials in this series, you’ll learn how to use Docker Compose to pair the Django container with an Nginx reverse proxy, and deploy this architecture to a Kubernetes cluster.

      It’s highly recommended to work through the tutorial to understand the changes you’re making to the app, but if you’d like to skip ahead, you can obtain the modified code from the polls-docker branch of the Polls app GitHub repository.

      Prerequisites

      To follow this tutorial, you’ll need:

      Step 1 — Creating the PostgreSQL Database and User

      To begin, we’ll connect to the PostgreSQL server from the Ubuntu instance. Then, we’ll create a PostgreSQL database and user for the Django app, and configure the database to work effectively with Django.

      Before we connect to the database from our Ubuntu machine (not the app container), we need to install the postgresql-client package from the Ubuntu repositories. First update the local apt package index and then download and install the package:

      sudo apt update
      sudo apt install postgresql-client
      

      Hit Y and then ENTER when prompted to begin downloading and installing the packages.

      Now that you’ve installed the client, we’ll use it to create a database and database user for our Django application.

      To begin, grab the Connection Parameters for your cluster by navigating to Databases from the Cloud Control Panel, and clicking into your database. You should see a Connection Details box containing some Connection parameters for your cluster. Note these down.

      Back on the command line, log in to your cluster using these credentials and the psql PostgreSQL client we just installed:

      psql -U username -h host -p port -d database -set=sslmode=require
      

      When prompted, enter the password displayed alongside the Postgres username, and hit ENTER.

      You will be given a PostgreSQL prompt from which you can manage the database.

      First, create a database for your project called polls:

      CREATE DATABASE polls;
      

      Note: Every Postgres statement must end with a semicolon, so make sure that your command ends with one if you are experiencing issues.

      We can now switch to the polls database:

      c polls;
      

      Next, create a database user for the project. Make sure to select a secure password:

      CREATE USER sammy WITH PASSWORD 'password';
      

      We’ll now modify a few of the connection parameters for the user we just created. This will speed up database operations so that the correct values do not have to be queried and set each time a connection is established.

      We are setting the default encoding to UTF-8, which Django expects. We are also setting the default transaction isolation scheme to “read committed”, which blocks reads from uncommitted transactions. Lastly, we are setting the timezone. By default, our Django projects will be set to use UTC. These are all recommendations from the Django project itself.

      Enter the following commands at the PostgreSQL prompt:

      ALTER ROLE sammy SET client_encoding TO 'utf8';
      ALTER ROLE sammy SET default_transaction_isolation TO 'read committed';
      ALTER ROLE sammy SET timezone TO 'UTC';
      

      Now we can give our new user access to administer our new database:

      GRANT ALL PRIVILEGES ON DATABASE polls TO sammy;
      

      When you are finished, exit out of the PostgreSQL prompt by typing:

      q
      

      A Django app, properly configured, can now connect to and manage this database. In the next step, we’ll clone the Polls app code from GitHub and explicitly define its Python package dependencies.

      Step 2 — Cloning App Repository and Declaring Dependencies

      To begin the process of containerizing our Django Polls app, we’ll first clone the django-polls repository, which contains the complete code for the Django project’s tutorial Polls app.

      Log in to your server, create a directory called polls-project and use git to clone the django-polls repo from GitHub:

      mkdir polls-project
      cd polls-project
      git clone https://github.com/do-community/django-polls.git
      

      Access the django-polls directory and list the repository contents:

      cd django-polls
      ls
      

      Output

      LICENSE README.md manage.py mysite polls templates

      You should see the following objects:

      • manage.py: The main command-line utility used to manipulate the app.
      • polls: Contains the polls app code.
      • mysite: Contains Django project-scope code and settings.
      • templates: Contains custom template files for the administrative interface.

      To learn more about the project structure and files, consult Creating a Project from the official Django documentation.

      In this directory we’ll also create a file called requirements.txt that will contain the Django app’s Python dependencies.

      Open a file called requirements.txt in your editor of choice and paste in the following Python dependencies:

      polls-project/django-polls/requirements.txt

      boto3==1.9.252
      botocore==1.12.252
      Django==2.2.6
      django-storages==1.7.2
      docutils==0.15.2
      gunicorn==19.9.0
      jmespath==0.9.4
      psycopg2==2.8.3
      python-dateutil==2.8.0
      pytz==2019.3
      s3transfer==0.2.1
      six==1.12.0
      sqlparse==0.3.0
      urllib3==1.25.6
      

      Here we install Django, the django-storages plugin for offloading static assets to object storage, the gunicorn WSGI server, the psycopg2 PostgreSQL adapter, as well as some additional dependency packages. Note that we explicitly list and version every Python package required by our app.

      Save and close the file.

      Now that we’ve cloned the app and defined its dependencies, we can move on to modifying it for portability.

      Step 3 — Making Django Configurable by Environment Variables

      One of the most important recommendations from the twelve-factor app methodology is extracting hard-coded config from your application’s codebase. This allows you to easily change the behavior of your application at runtime by modifying environment variables. Docker and Kubernetes both suggest this method of configuring containers, so we will adapt our application’s settings file to use this pattern.

      The main settings file for our Django project (django-polls/mysite/settings.py) is a Python module that uses native data structures to configure the application. By default, most of the values in the file are hard-coded, meaning that you have to edit the configuration file to change the application behavior. We can use Python’s getenv function in the os module to configure Django to read configuration parameters from local environment variables instead.

      To do this, we’ll go through settings.py and replace the hard-coded values of each of the variables we want to set at runtime with a call to os.getenv. The os.getenv function reads the value from a provided environment variable name. You can optionally provide a second parameter with a default value that will be used if the environment variable is not set.

      This allows us to set variables like this:

      polls-project/django-polls/mysite/settings.py

      . . .
      SECRET_KEY = os.getenv('DJANGO_SECRET_KEY')
      . . .
      DEBUG = os.getenv('DJANGO_DEBUG', False)
      . . .
      

      For SECRET_KEY, Django will look for an environment variable called DJANGO_SECRET_KEY. Since this shouldn’t be hard-coded and needs to be the same across our application servers, we’ll want to set this externally with no fallback value. We want the application to fail if we do not provide this, since it could lead to problems if various copies of our application use different keys.

      For DEBUG, Django will look for an environment variable called DJANGO_DEBUG. However, this time, we’ve provided a default value that will be used as fallback if the variable is not set. In this case, we’ve opted to set DEBUG to False if no value is provided so that we do not accidentally leak sensitive information unless the variable is intentionally defined and set to True..

      To apply this technique, open the polls-project/django-polls/mysite/settings.py file in your editor of choice, and move through it, externalizing the following variables with the provided default values:

      • SECRET_KEY = os.getenv('DJANGO_SECRET_KEY')
      • DEBUG = os.getenv('DEBUG', False)
      • ALLOWED_HOSTS = os.getenv('DJANGO_ALLOWED_HOSTS', '127.0.0.1').split(',')

      For ALLOWED_HOSTS, we fetch the DJANGO_ALLOWED_HOSTS environment variable, and split it into a Python list using , as a separator. If the variable isn’t set, ALLOWED_HOSTS is set to 127.0.0.1.

      Once you’ve modified the above variables, navigate to the DATABASES variable and configure it as follows:

      polls-project/django-polls/mysite/settings.py

      . . . 
      # Database
      # https://docs.djangoproject.com/en/2.1/ref/settings/#databases
      
      DATABASES = {
           'default': {
               'ENGINE': 'django.db.backends.{}'.format(
                   os.getenv('DATABASE_ENGINE', 'sqlite3')
               ),
               'NAME': os.getenv('DATABASE_NAME', 'polls'),
               'USER': os.getenv('DATABASE_USERNAME', 'myprojectuser'),
               'PASSWORD': os.getenv('DATABASE_PASSWORD', 'password'),
               'HOST': os.getenv('DATABASE_HOST', '127.0.0.1'),
               'PORT': os.getenv('DATABASE_PORT', 5432),
               'OPTIONS': json.loads(
                   os.getenv('DATABASE_OPTIONS', '{}')
               ),
           }
       }
       . . .
      

      This will set the default database parameters using environment variables.

      For DATABASES['default']['OPTIONS'], we used json.loads to deserialize a JSON object passed in through the DATABASE_OPTIONS environment variable. Most of the time, interpreting environment variables as simple strings makes the translation to Django settings easier to read. However, in this instance, being able to pass in an arbitrary data structure is valuable. Each database engine has a unique set of valid options, so being able to encode a JSON object with the appropriate parameters gives us much greater flexibility at the expense of some legibility.

      To make use of the json library, import it at the top of settings.py:

      polls-project/django-polls/mysite/settings.py

      """
      Django settings for mysite project.
      
      Generated by 'django-admin startproject' using Django 2.1.
      
      For more information on this file, see
      https://docs.djangoproject.com/en/2.1/topics/settings/
      
      For the full list of settings and their values, see
      https://docs.djangoproject.com/en/2.1/ref/settings/
      """
      
      import os
      import json
      . . .
      

      The other area that requires special attention is DATABASES['default']['NAME']. For most database engines, this is the database name within the relational database management system. On the other hand, if you’re using SQLite, NAME is used to specify the database file so be sure to set this parameter accordingly.

      Since the settings.py file is Python code, there are many different ways you can handle reading values from the environment. The method we’ve used here is just one possible technique for externalizing configuration from your codebase.

      In this step we’ve configured the main Django settings variables in a generic and portable fashion, including the database parameters. In the following step, we’ll continue configuring settings for static files like Javascript and CSS stylesheets, which we’ll centralize and offload to an S3-compatible object storage service.

      Step 4 — Offloading Static Assets

      When running multiple Django containers in a production environment, it can be cumbersome to maintain specific versions of static assets and files across the entire fleet of running containers. To streamline this architecture, we can offload all shared elements and state to external storage. Instead of trying to keep these items in sync across replicas or implementing backup and loading routines to ensure data is locally available, we can implement access to these assets as network-accessible services.

      In the last step, we configured Django so that we could pass in database connection parameters through environment variables. In this step, we’ll do the same for our object storage service, which we’ll use to store static assets that will be shared by Django containers.

      The django-storages package provides remote storage backends (including S3-compatible object storage) that Django can use to offload files. We’ll configure the Polls app to use django-storages to upload static files to a DigitalOcean Space, as outlined in Step 7 of How to Set Up a Scalable Django App with DigitalOcean Managed Databases and Spaces. In this guide, we’ll use DigitalOcean Spaces, but you can use any S3-compatible object storage provider.

      To begin, we’ll make some modifications to the same django-polls/mysite/settings.py file we’ve altered in previous steps.

      Begin by opening up the mysite/settings.py file for editing and appending the storages app to Django’s list of INSTALLED_APPS:

      polls-project/django-polls/mysite/settings.py

      . . .
      INSTALLED_APPS = [
          . . .
          'django.contrib.staticfiles',
          'storages',
      ]
      . . .
      

      The storages app is installed via django-storages in the requirements.txt file we defined in Step 1.

      Now, locate the STATIC_URL variable at the bottom of the file, and replace it with the following block:

      polls-project/django-polls/mysite/settings.py

      . . .
      
      # Static files (CSS, JavaScript, Images)
      # https://docs.djangoproject.com/en/2.1/howto/static-files/
      
      # Moving static assets to DigitalOcean Spaces as per:
      # https://www.digitalocean.com/community/tutorials/how-to-set-up-object-storage-with-django
      AWS_ACCESS_KEY_ID = os.getenv('STATIC_ACCESS_KEY_ID')
      AWS_SECRET_ACCESS_KEY = os.getenv('STATIC_SECRET_KEY')
      
      AWS_STORAGE_BUCKET_NAME = os.getenv('STATIC_BUCKET_NAME')
      AWS_S3_ENDPOINT_URL = os.getenv('STATIC_ENDPOINT_URL')
      AWS_S3_OBJECT_PARAMETERS = {
          'CacheControl': 'max-age=86400',
      }
      AWS_LOCATION = 'static'
      AWS_DEFAULT_ACL = 'public-read'
      
      STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
      
      STATIC_URL = '{}/{}/'.format(AWS_S3_ENDPOINT_URL, AWS_LOCATION)
      STATIC_ROOT = 'static/'
      

      We hard-code the following configuration variables:

      • STATICFILES_STORAGE: Sets the storage backend Django will use to offload static files. This S3Boto3Storage backend should work with any S3-compatible backend, including DigitalOcean Spaces.
      • AWS_S3_OBJECT_PARAMETERS Sets the cache control headers on static files.
      • AWS_LOCATION: Defines a directory called static within the object storage bucket where all static files will be placed.
      • `AWS_DEFAULT_ACL: Defines the access control list (ACL) for the static files. Setting it to public-read ensures that the files are publicly accessible to end users.
      • STATIC_URL: Specifies the base URL that Django should use when generating URLs for static files. Here, we combine the endpoint URL and the static files subdirectory to construct a base URL for static files.
      • STATIC_ROOT: Specifies where to collect static files locally before copying them to object storage.

      To maintain flexibility and portability, we set up many of the parameters to be configurable at runtime using environment variables, just as we did previously. These include:

      • AWS_ACCESS_KEY_ID: Set by the STATIC_ACCESS_KEY_ID environment variable. The DigitalOcean Spaces access key identifier.
      • AWS_SECRET_ACCESS_KEY: Set by STATIC_SECRET_KEY. The DigitalOcean Spaces secret key.
      • AWS_STORAGE_BUCKET_NAME: Set by STATIC_BUCKET_NAME. The object storage bucket to which Django will upload assets.
      • AWS_S3_ENDPOINT_URL: Set by STATIC_ENDPOINT_URL. The endpoint URL used to access the object storage service. For DigitalOcean Spaces, this will be something like https://nyc3.digitaloceanspaces.com, depending on the region where your Spaces bucket is located.

      When you’re done making changes to settings.py, save and close the file.

      From now on, when you run manage.py collectstatic to assemble your project’s static files, Django will upload these to remote object storage. Django is also now configured to serve static assets from this object storage service.

      At this point, if you’re using a DigitalOcean Space, you can optionally enable a CDN for your Space, which will speed up delivery of your Django project’s static files by caching them across a geographically-distributed network of edge servers. You can also optionally configure a custom subdomain for your Space. To learn more about CDNs, consult Using a CDN to Speed Up Static Content Delivery. Configuring a CDN goes beyond the scope of this tutorial, but the steps very closely match those in the Enabling CDN section of How to Set Up a Scalable Django App with DigitalOcean Managed Databases and Spaces.

      In the next step, we’ll make a final set of changes to settings.py which will enable Django logging to STDOUT and STDERR so that these streams can be picked up by the Docker Engine and inspected using docker logs.

      Step 5 — Configuring Logging

      By default, Django logs information to standard output and standard error when running the development HTTP server or when the DEBUG option is set to True. However, when DEBUG is set to False or when using a different HTTP server, both of which are likely true in production environments, Django uses a different logging mechanism. Instead of logging everything of priority INFO and above to standard streams, it sends messages of priority ERROR or CRITICAL to an administrative email account.

      This makes sense for many situations, but in Kubernetes and containerized environments, logging to standard output and standard error is highly recommended. Logging messages are collected in a centralized directory on the Node’s filesystem and are accessible interactively using kubectl and docker commands. This Node-level aggregation facilitates log collection by allowing operations teams to run a process on each node to watch and forward logs. To leverage this architecture, the application must write its logs to these standard sinks.

      Fortunately, logging in Django uses the highly configurable logging module from the Python standard library, so we can define a dictionary to pass to logging.config.dictConfig to define our desired outputs and formatting. To learn more about this technique and others for configuring Django logging, consult Django Logging, The Right Way.

      Once again, open up django-polls/mysite/settings.py in your editor.

      We’ll first add an additional import statement to the top of the file so that we can manipulate the logging configuration:

      polls-project/django-polls/mysite/settings.py

      import json
      import os
      import logging.config
      . . .
      

      The logging.config import allows us to override Django’s default logging behavior by passing in a dictionary of new logging configuration to the dictConfig function.

      Now, navigate to the bottom of the file, and paste in the following block of logging configuration code:

      polls-project/django-polls/mysite/settings.py

      . . .
      # Logging Configuration
      
      # Clear prev config
      LOGGING_CONFIG = None
      
      # Get loglevel from env
      LOGLEVEL = os.getenv('DJANGO_LOGLEVEL', 'info').upper()
      
      logging.config.dictConfig({
          'version': 1,
          'disable_existing_loggers': False,
          'formatters': {
              'console': {
                  'format': '%(asctime)s %(levelname)s [%(name)s:%(lineno)s] %(module)s %(process)d %(thread)d %(message)s',
              },
          },
          'handlers': {
              'console': {
                  'class': 'logging.StreamHandler',
                  'formatter': 'console',
              },
          },
          'loggers': {
              '': {
                  'level': LOGLEVEL,
                  'handlers': ['console',],
              },
          },
      })
      

      Here, we set LOGGING_CONFIG to None to disable the default logging configuration provided by Django. We set LOGLEVEL to INFO by default, but check the DJANGO_LOGLEVEL environment variable so that we can override as necessary.

      Finally, we use the dictConfig function to set a new configuration dictionary using the logging.config module. In the dictionary, we define the text format using formatters, define the output by setting up handlers, and configure which messages should go to each handler using loggers.

      This is a fairly minimal configuration that allows you to specify a logging severity level using an environment variable called DJANGO_LOGLEVEL, and then log all messages at or above that level to standard streams. For an in-depth discussion of Django logging mechanisms, consult Logging from the official Django docs.

      With this configuration, when we containerize the application, Docker will expose these logs through the docker logs command. Likewise, Kubernetes will capture the output and expose it through the kubectl logs command.

      This concludes our code modifications to the Django Polls app. In the next step, we’ll begin the containerization process by writing the app’s Dockerfile.

      Step 6 — Writing the Application Dockerfile

      In this step we’ll define the container image that will run our Django app and the Gunicorn WSGI server that will serve it. It involves building a container image by defining the runtime environment, installing the application and its dependencies, and completing some basic configuration. While there are many possible ways to encapsulate an application in a container image, the practices followed in this step produce a slim, streamlined app image.

      Choosing an Appropriate Parent Image

      The first major decision that you will have to make when building a container image is the foundation to build from. Container images can either be built from SCRATCH, indicating an empty filesystem, or from an existing container image. Many different base container images are available, each defining a filesystem and providing a unique set of preinstalled packages. Images based on vanilla Linux distributions like Ubuntu 18.04 provide a generic operating environment, while more specialized images often include common libraries and tooling for specific programming languages.

      Whenever possible, it’s often a good idea to use an image from one of Docker’s official repositories as a base. These images have been verified by Docker to follow best practices and are updated regularly for security fixes and improvements.

      Since our application is built with Django, an image with a standard Python environment will provide a solid foundation and include many of the tools we need to get started. The official Docker repository for Python offers a wide selection of Python-based images, each installing a version of Python and some common tooling on top of an operating system.

      While the appropriate level of functionality depends on your use case, images based on Alpine Linux are often a solid jumping off point. Alpine Linux offers a robust, but minimal, operating environment for running applications. Its default filesystem is very small, but includes a complete package management system with fairly extensive repositories to make adding functionality straightforward.

      Note: You may have noticed in the list of tags for Python images that multiple tags are available for each image. Docker tags are mutable and maintainers can reassign the same tag to a different image in the future. As a result, many maintainers provide sets of tags with varying degrees of specificity to allow for different use cases. For example, the tag 3-alpine is used to point to the latest available Python 3 version on the latest Alpine version, so it will be reassigned to a different image when a new version of Python or Alpine is released. To make image builds more deterministic, it’s best to use the most specific tags you can find for the image you want to use.

      In this guide, we’ll use the Python image tagged as 3.7.4-alpine3.10 as the parent image for our Django application. We specify the repository and tag of the parent image in our Dockerfile using the FROM instruction.

      First, navigate out of the django-polls directory.

      Then, open a file called Dockerfile in your editor of choice. Paste in the following parent image definition:

      polls-project/Dockerfile

      FROM python:3.7.4-alpine3.10
      

      This defines the starting point for the custom Docker image we are building to run our application.

      Adding Instructions to Set Up the Application

      Once you’ve chosen a parent image, you can begin adding instructions to install dependencies, copy over our application files, and set up the running environment. This process generally mirrors the steps you would take to set up a server for your application, with some key differences to account for the container abstractions.

      After the FROM line, paste in the following block of Dockerfile code:

      polls-project/Dockerfile

      . . .
      
      ADD django-polls/requirements.txt /app/requirements.txt
      
      RUN set -ex 
          && apk add --no-cache --virtual .build-deps postgresql-dev build-base 
          && python -m venv /env 
          && /env/bin/pip install --upgrade pip 
          && /env/bin/pip install --no-cache-dir -r /app/requirements.txt 
          && runDeps="$(scanelf --needed --nobanner --recursive /env 
              | awk '{ gsub(/,/, "nso:", $2); print "so:" $2 }' 
              | sort -u 
              | xargs -r apk info --installed 
              | sort -u)" 
          && apk add --virtual rundeps $runDeps 
          && apk del .build-deps
      
      ADD django-polls /app
      WORKDIR /app
      
      ENV VIRTUAL_ENV /env
      ENV PATH /env/bin:$PATH
      
      EXPOSE 8000
      

      Let’s go over these instructions to explain some of the less obvious choices. To learn even more about building production-ready Dockerfiles for Django apps, consult A Production-Ready Dockerfile for your Django App.

      First Docker will copy the requirements.txt file to /app/requirements.txt so that our application’s dependencies are available on the image’s filesystem. We will use this to install all of the Python packages that our application needs in order to run. We copy the dependencies file as a separate step from the rest of our codebase so that Docker can cache the image layer containing the dependencies file. Any time the requirements.txt file doesn’t change between builds, Docker can then reuse the cached layer instead of rebuilding it, speeding up the process.

      Next, we have a single RUN instruction that executes a long list of commands, each chained together using the Linux && operator. To summarize, these commands:

      • Install the PostgreSQL development files and basic build dependencies using Alpine’s apk package manager
      • Create a virtual environment
      • Install the Python dependencies listed in requirements.txt with pip
      • Compile a list of packages needed at runtime by analyzing the requirements of the installed Python packages
      • Uninstall any unneeded build dependencies

      We chain the commands together instead of executing each in a separate RUN step because of the way that Docker constructs image layers. For each ADD, COPY, and RUN instruction, Docker creates a new image layer on top of the existing filesystem, executes the instruction, and then saves the resulting layer. This means compressing commands in RUN instructions will result in fewer image layers.

      Once an item has been written to an image layer, it cannot be removed in a subsequent layer to reduce the image size. If we install build dependencies but want to remove them once the application is set up, we need to do so within the same instruction to reduce the image size. In this RUN command, we install build dependencies, use them to build the app’s packages, and subsequently remove them using apk del.

      After the RUN instruction, we use ADD to copy in the application code and WORKDIR to set the working directory for the image to our code directory.

      Then, we use the ENV instruction to set two environment variables that will be available within containers spawned from our image. The first one sets VIRTUAL_ENV to /env and the second instruction modifies the PATH variable to include the /env/bin directory. These two lines emulate the results of sourcing the /env/bin/activate script, which is the traditional method of activating a virtual environment.

      Finally, we use EXPOSE to inform Docker that the container will listen on port 8000 at runtime.

      At this point, the Dockerfile is nearly complete. We just need to define the default command that will run when we start containers using the image.

      Defining the Default Command

      A Docker image’s default command determines what happens when a container is started without explicitly providing a command to execute. ENTRYPOINT and CMD instructions can be used independently or in tandem to define a default command within the Dockerfile.

      When both ENTRYPOINT and CMD are defined, the ENTRYPOINT defines the executable that will be run by the container, and the CMD represents the default argument list for that command. Users can override the default argument list by appending alternative arguments on the command line: docker run <image> <arguments>. In this format, users will be unable to easily override the ENTRYPOINT command, so the ENTRYPOINT command is often set to a script that will set up the environment and perform different actions based on the argument list it receives.

      When used alone, ENTRYPOINT configures the container’s executable, but does not define a default argument list. If only CMD is set, it will be interpreted as the default command and argument list, which can be overridden at runtime.

      In our image, we want the container to run our application by default using the gunicorn application server. The argument list that we pass to gunicorn doesn’t need to be configurable at runtime, but we want the ability to easily run other commands if necessary to debug or perform management tasks (like collecting static assets or initializing the database). With these requirements in mind, it makes sense for us to use CMD to define a default command with no ENTRYPOINT.

      The CMD instruction can be defined using any of the following formats:

      • CMD ["argument 1", "argument 2", . . . ,"argument n"]: The argument list format (used to define the default argument list for an ENTRYPOINT)
      • CMD ["command", "argument 1", "argument 2", . . . ,"argument n"]: The exec format
      • CMD command "argument 1" "argument 2" . . . "argument n": The shell format

      The first format only lists arguments and is used in conjunction with an ENTRYPOINT. The other two formats specify commands and their arguments, with a few key differences. The exec format, which is recommended, executes the command directly, passing in the argument list with no shell processing. The shell format, on the other hand, passes the entire list to sh -c. This is necessary if, for example, you need to substitute the value of an environment variable in a command, but is generally regarded as less predictable.

      For our purposes, the final instruction in our Dockerfile looks like this:

      polls-project/Dockerfile

      . . .
      CMD ["gunicorn", "--bind", ":8000", "--workers", "3", "mysite.wsgi:application"]
      

      By default, containers using this image will execute gunicorn bound to localhost port 8000 with 3 workers, and run the application function in the wsgi.py file found in the mysite directory. You can optionally provide a command at runtime to execute a different process instead of gunicorn.

      At this point you can use docker build to build your app image and docker run to run the container on your machine.

      Building the Docker Image

      By default, the docker build command looks for a Dockerfile in the current directory to find its build instructions. It also sends the build “context”, the local filesystem hierarchy that should be available during the build process, to the Docker daemon. Often, the current directory is set as the build context.

      After accessing the directory containing your Dockerfile, run docker build, passing in an image and tag name with the -t flag, and use the current directory as build context. Here, we name the image django-polls and tag it with version v0:

      • docker build -t django-polls:v0 .

      The command will pass the Dockerfile and current directory as the build context to the Docker daemon. The daemon will build your image by creating a series of image layers as it processes the Dockerfile instructions.

      When docker build completes, you should see the following output:

      Output

      Successfully built 8260b58f5713 Successfully tagged django-polls:v0

      After successfully building the image, you’re able to run the app container using docker run. However, the run command will most likely fail here as we still haven’t configured the container’s running environment. Externalized variables like SECRET_KEY and database settings from settings.py will be either blank or set to default values.

      In the final step, we’ll configure the container’s running environment using an environment variable file. Then, we’ll create the database schema, generate and upload the app’s static files to object storage, and finally test the app.

      Step 7 — Configuring the Running Environment and Testing the App

      Docker provides several methods for setting environment variables inside of the container. Since we have to set all of the variables we externalized in Step 1, we’ll use the --env-file method, which allows us to pass in a file containing a list of environment variables and their values.

      Create a file called env in the polls-project directory, and paste in the following list of variables:

      polls-project/env

      DJANGO_SECRET_KEY=your_secret_key
      DEBUG=True
      DJANGO_ALLOWED_HOSTS=your_server_IP_address
      DATABASE_ENGINE=postgresql_psycopg2
      DATABASE_NAME=polls
      DATABASE_USERNAME=sammy
      DATABASE_PASSWORD=your_database_password
      DATABASE_HOST=your_database_host
      DATABASE_PORT=your_database_port
      STATIC_ACCESS_KEY_ID=your_space_access_key_id
      STATIC_SECRET_KEY=your_space_secret_key
      STATIC_BUCKET_NAME=your_space_name
      STATIC_ENDPOINT_URL=https://nyc3.digitaloceanspaces.com
      DJANGO_LOGLEVEL=info
      

      Replace the following values in this file:

      • DJANGO_SECRET_KEY: Set this to a unique, unpredictable value, as detailed in the Django docs. One method of generating this key is provided in Adjusting the App Settings of the Scalable Django App tutorial.
      • DJANGO_ALLOWED_HOSTS: Set this to the IP address of your Ubuntu server. For testing purposes, you can also set it to *, a wildcard that will match all hosts. Be sure to set this value appropriately when running Django in a production environment.
      • DATABASE_USERNAME: Set this to the database user created in the previous step.
      • DATABASE_PASSWORD: Set this to the user password created in the previous step.
      • DATABASE_HOST: Set this to your database’s hostname.
      • DATABASE_PORT: Set this to your databases’s port.
      • STATIC_ACCESS_KEY_ID: Set this to your Space’s access key.
      • STATIC_SECRET_KEY: Set this to your Space’s access key Secret.
      • STATIC_BUCKET_NAME: Set this to your Space name.
      • STATIC_ENDPOINT_URL: Set this to the appropriate Space endpoint URL.

      When running Django in production, be sure to set DEBUG to False and adjust the log level according to your desired verbosity.

      Save and close the file.

      We’ll now use docker run to override the CMD set in the Dockerfile and create the database schema using the manage.py makemigrations and manage.py migrate commands:

      • docker run --env-file env django-polls:v0 sh -c "python manage.py makemigrations && python manage.py migrate"

      Here, we run the django-polls:v0 container image, pass in the environment variable file we just created, and override the Dockerfile command with sh -c "python manage.py makemigrations && python manage.py migrate", which will create the database schema defined by the app code. After running the command you should see:

      Output

      No changes detected Operations to perform: Apply all migrations: admin, auth, contenttypes, polls, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying auth.0010_alter_group_name_max_length... OK Applying auth.0011_update_proxy_permissions... OK Applying polls.0001_initial... OK Applying sessions.0001_initial... OK

      This indicates that the database schema has successfully been created.

      Next, we’ll run another instance of the app container and use an interactive shell inside of it to create an administrative user for the Django project.

      • docker run -i -t --env-file env django-polls:v0 sh

      This will provide you with a shell prompt inside of the running container which you can use to create the Django user:

      • python manage.py createsuperuser

      Enter a username, email address, and password for your user, and after creating the user, hit CTRL+D to quit the container and kill it.

      Finally, we’ll generate the static files for the app and upload them to the DigitalOcean Space using collectstatic:

      • docker run --env-file env django-polls:v0 sh -c "python manage.py collectstatic --noinput"

      Output

      121 static files copied.

      We can now run the app:

      • docker run --env-file env -p 80:8000 django-polls:v0

      Output

      [2019-10-17 21:23:36 +0000] [1] [INFO] Starting gunicorn 19.9.0 [2019-10-17 21:23:36 +0000] [1] [INFO] Listening at: http://0.0.0.0:8000 (1) [2019-10-17 21:23:36 +0000] [1] [INFO] Using worker: sync [2019-10-17 21:23:36 +0000] [7] [INFO] Booting worker with pid: 7 [2019-10-17 21:23:36 +0000] [8] [INFO] Booting worker with pid: 8 [2019-10-17 21:23:36 +0000] [9] [INFO] Booting worker with pid: 9

      Here, we run the default command defined in the Dockerfile, gunicorn --bind :8000 --workers 3 mysite.wsgi:application, and expose container port 8000 so that port 80 on the Ubuntu server gets mapped to port 8000 of the django-polls:v0 container.

      You should now be able to navigate to the polls app using your web browser by typing http://your_server_ip in the URL bar. Since there is no route defined for the / path, you’ll likely receive a 404 Page Not Found error, which is expected.

      Navigate to http://your_server_ip/polls to see the Polls app interface:

      Polls App Interface

      To check out the admin interface, visit http://your_server_ip/admin. You should see the Polls app admin authentication window:

      Polls Admin Auth Page

      Enter the administrative username and password you created with the createsuperuser command.

      After authenticating, you can access the Polls app’s administrative interface:

      Polls Admin Main Interface

      Note that static assets for the admin and polls apps are being delivered directly from object storage. To confirm this, consult Testing Spaces Static File Delivery.

      When you are finished exploring, hit CTRL-C in the terminal window running the Docker container to kill the container.

      Conclusion

      In this tutorial you adapted a Django web app to work effectively in a container-based, cloud-native environment. You then wrote a minimal Dockerfile for the container image, built it locally, and ran it using Docker Engine. You can see a diff of the changes you implemented in the polls-docker branch of the Polls app GitHub repository. This branch contains all the modifications described in this tutorial.

      From here, you can pair the Django/Gunicorn container with an Nginx reverse proxy container to handle and route incoming HTTP requests, and a Certbot container to obtain TLS certificates. You can manage this multi-container architecture using Docker Compose; this will be described in a subsequent tutorial.

      Note that as-is, this setup is not production ready as you should always run Gunicorn behind an HTTP proxy to buffer slow clients. If not, your Django web app will be vulnerable to denial-of-service attacks. We also chose 3 as an arbitrary number of Gunicorn workers in this tutorial. In production, you should set the number of workers and threads using performance benchmarks.

      In this architecture, we made a design choice to offload static assets to object storage so that containers wouldn’t have to bundle a version of these assets and serve them using Nginx, which can become cumbersome to manage in multi-container cluster environments like Kubernetes. Depending on your use case, this may not be an effective design, so you should adapt the steps in this tutorial accordingly.

      Finally, now that you’ve fully containerized the Django Polls app, you can push the image to a container registry like Dockerhub and make it available to any system where Docker is available: Ubuntu servers, virtual machines, and container clusters like Kubernetes.



      Source link

      How To Set Up a PageKite Front-End Server on Debian 9


      The author selected the Open Internet/Free Speech Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      Private networks generally provide internet access to the hosts using NAT (network address translation), sharing a single public IP address with all hosts inside the private network. In NAT systems, the hosts inside the private network are not visible from outside the network. To expose services running on these hosts to the public internet, you would usually create NAT rules in the gateway, commonly called port forwarding rules. In several situations, though, you wouldn’t have access to the gateway to configure these rules. For situations such as this, tunneling solutions like PageKite come in handy.

      PageKite is a fast and secure tunneling solution that can expose a service inside a private network to the public internet without the need for port forwarding. To do this, it relies on an external server, called the front-end server, to which the server behind NAT and the clients connect to allow communication between them. By default, PageKite uses its own commercial pagekite.net service, but as it is a completely open-source project, it allows you to set up a private frontend on a publicly accessible host, such as a DigitalOcean Droplet. With this setup, you can create a vendor-independent solution for remote access to hosts behind NAT. By configuring the remote hosts with the PageKite client to connect to the frontend and exposing the SSH port, it is possible to access them via the command line interface shell using SSH. It’s also possible to access a graphical user interface using a desktop sharing system such as VNC or RDP running over an SSH connection.

      In this tutorial, you will install and set up a PageKite front-end service on a server running Debian 9. You will also set up two more Debian 9 servers to simulate a local and a remote environment. When you’re finished, you will have set up a server for multiple clients, and tested it with a practical solution for remote access using SSH and VNC.

      Prerequisites

      Before following this guide you’ll need the following:

      • A DigitalOcean account to set up the Droplets that will be used in the tutorial.
      • A server running Debian 9 with a public IP address to act as the front-end server, set up according to the Initial Server Setup with Debian 9 guide. A standard DigitalOcean Droplet with 1GB of memory is enough for testing purposes or for applications with a few connections. We’ll refer to this server by the host name front-end-server and its public IP address by Front_End_Public_IP.
      • Two hosts running Debian 9, which will play the role of a remote and local host that will connect using the PageKite service, set up according to the Initial Server Setup with Debian 9 guide. The remote host, with internet access through NAT, will be accessed by the local host using a PageKite tunnel. Remote and local hosts will be referred to by the host names remote-host and local-host and their public IP addresses by Remote_Host_Public_IP and Local_Host_Public_IP respectively. This tutorial will use two standard DigitalOcean Droplets with 1GB of memory to represent them. Alternatively, two local or virtual machines could be used to represent these hosts.
      • A fully registered domain name. This tutorial will use your_domain as an example throughout. You can purchase a domain name on Namecheap, get one for free on Freenom, or use the domain registrar of your choice.
      • Both of the following DNS records set up for your server. You can follow this introduction to DigitalOcean DNS for details on how to add them.
        • An A record with pagekite.your_domain pointing to the IP address of the front-end-server.
        • We also need to set up DNS so that every domain ending with pagekite.your_domain also points out to our front-end-server. This can be set up using wildcard DNS entries. In this case, create an A record for the wildcard DNS entry *.pagekite.your_domain to point out to the same IP address, Front_End_Public_IP. This will be used to distinguish the clients that connect to our server by domain name (client-1.pagekite.your_domain and client-2.pagekite.your_domain, for example) and tunnel the requisitions appropriately.
      • A local computer with a VNC client installed that supports VNC connections over SSH tunnels.
        • On Windows, you can use TightVNC, RealVNC, or UltraVNC.
        • On macOS, you can use the built-in Screen Sharing program, or can use a cross-platform app like RealVNC.
        • On Linux, you can choose from many options, including vinagre, krdc, RealVNC, or TightVNC.

      Step 1 — Setting Up the Servers

      In this tutorial, we are going to use three DigitalOcean Droplets to play the role of front-end-server, local-host, and remote-host. To do this, we will first set the local-host and remote-host up to have access to the graphical environment and to mimic the behavior of a remote-host under NAT, so that PageKite can be used as a solution to access its services. Besides that, we also need to configure the front-end-server Droplet firewall rules to allow it to work with PageKite and intermediate the connection between local-host and remote-host.

      As we are going to work with multiple servers, we’re going to use different colors in the command listings to identify which server we are using, as follows:

      • # Commands and outputs in the front-end-server Droplet
      • # Commands and outputs in the remote-host Droplet
      • # Commands and outputs in the local-host Droplet
      • # Commands and outputs in both the remote-host and local-host Droplets

      Let’s first go through the steps for both remote-host and local-host Droplets, to install the dependencies and set up access to the graphical environment using VNC. After that, we will cover the firewall configuration in each of the three Droplets to allow the front-end-server to run PageKite and mimic a connection using NAT on remote-host.

      Installing Dependencies

      We will need access to the graphical interface on both local-host and remote-host hosts to run through this demonstration. On local-host, we will use a VNC session to access its graphical interface and test our setup using the browser. On remote-host, we will set up a VNC session that we will access from local-host.

      To set up VNC, first we need to install some dependencies on local-host and remote-host. But before installing any package, we need to update the package list of the repositories, by running the following on both servers:

      Next, we install the VNC server and a graphical user environment, which is needed to start a VNC session. We will use the Tight VNC server and the Xfce desktop environment, which can be installed by running:

      • sudo apt-get install xfce4 xfce4-goodies tightvncserver

      In the middle of the graphical environment installation, we’ll be asked about the keyboard layout we wish to use. For a QWERTY US keyboard, select English (US).

      In addition to these, on local-host we’re going to need a VNC viewer and an internet browser to be able to perform the connection to remote-host. This tutorial will install the Firefox web browser and the xtightvncviewer. To install them, run:

      • sudo apt-get install firefox-esr xtightvncviewer

      When a graphical environment is installed, the system initializes in graphical mode by default. By using the DigitalOcean console, it is possible to visualize the graphical login manager, but it is not possible to log in or to use the command line interface. In our setup, we are mimicking the network behavior as if we were using NAT. To do this, we will need to use the DigitalOcean console, since we won’t be able to connect using SSH. Therefore, we need to disable the graphical user interface from automatically starting on boot. This can be done by disabling the login manager on both servers:

      • sudo systemctl disable lightdm.service

      After disabling the login manager, we can restart the Droplets and test if we can log in using the DigitalOcean console. To do that, run the following:

      Next, access the DigitalOcean console by navigating to the Droplet page in the DigitalOcean Control Panel, selecting your local-host Droplet, and clicking on the word Console in the top right corner, near the switch to turn the Droplet on and off:

      DigitalOcean Control Panel

      Once you press enter in the console, you will be prompted for your username and password. Enter these credentials to bring up the command line prompt:

      DigitalOcean Droplet Console

      Once you have done this for the local-host, repeat for the remote-host.

      With the console up for both Droplets, we can now set up the VNC.

      Setting Up VNC

      Here, we will put together a basic VNC setup. If you would like a more in-depth guide on how to set this up, check out our How to Install and Configure VNC on Debian 9 tutorial.

      To start a VNC session, run the following on both local-host and remote-host Droplets:

      On the first run, the system will create the configuration files and ask for the main password. Input your desired password, then verify it. The VNC server will also ask for a view-only password, used for viewing another user’s VNC session. As we won’t need a view-only VNC session, type n for this prompt.

      The ouput will look similar to this:

      Output

      sammy@remote-host:/home/sammy$ vncserver You will require a password to access your desktops. Password: Verify: Would you like to enter a view-only password (y/n)? n xauth: file /home/sammy/.Xauthority does not exist New 'X' desktop is remote-host:1 Creating default startup script /home/sammy/.vnc/xstartup Starting applications specified in /home/sammy/.vnc/xstartup Log file is /home/sammy/.vnc/remote-host:1.log

      The :1 after the host name represents the number of the VNC session. By default, the session number 1 is run on port 5901, session number 2 on port 5902, and so on. Following the previous output, we can access remote-host by using a VNC client to connect to Remote_Host_Public_IP on port 5901.

      One problem of the previous configuration is that it is not persistent, which means it won’t be started by default when the Droplet is restarted. To make it persistent, we can create a Systemd service and enable it. To do that, we will create the vncserver@.service file under /etc/systemd/system, which can be done using nano:

      • sudo nano /etc/systemd/system/vncserver@.service

      Place the following contents in the file, replacing sammy with your username:

      /etc/systemd/system/vncserver@.service

      [Unit]
      Description=Start TightVNC server at startup
      After=syslog.target network.target
      
      [Service]
      Type=forking
      User=sammy
      PAMName=login
      PIDFile=/home/sammy/.vnc/%H:%i.pid
      ExecStartPre=-/usr/bin/vncserver -kill :%i > /dev/null 2>&1
      ExecStart=/usr/bin/vncserver -depth 24 -geometry 1280x800 :%i
      ExecStop=/usr/bin/vncserver -kill :%i
      
      [Install]
      WantedBy=multi-user.target
      

      This file creates a vncserver Systemd unit, which can be configured as a system service using the systemctl tool. In this case, when the service is started, it kills the VNC session if it is already running (line ExecStartPre) and starts a new session using the resolution set to 1280x800 (line ExecStart). When the service is stopped, it kills the VNC session (line ExecStop).

      Save the file and quit nano. Next, we’ll make the system aware of the new unit file by running:

      • sudo systemctl daemon-reload

      Then, enable the service to be automatically started when the server is initialized by running:

      • sudo systemctl enable vncserver@1.service

      When we use the enable command with systemctl, symlinks are created so that the service is started automatically when the system is initialized, as informed by the output of the previous command:

      Output

      Created symlink /etc/systemd/system/multi-user.target.wants/vncserver@1.service → /etc/systemd/system/vncserver@.service.

      With the VNC server properly configured, we may restart the Droplets to test if the service is automatically started:

      After the system initializes, log in using SSH and check if VNC is running with:

      • sudo systemctl status vncserver@1.service

      The output will indicate the service is running:

      ● vncserver@1.service - Start TightVNC server at startup
         Loaded: loaded (/etc/systemd/system/vncserver@.service; enabled; vendor preset: enabled)
         Active: active (running) since Thu 2019-08-29 19:21:12 UTC; 1h 22min ago
        Process: 848 ExecStart=/usr/bin/vncserver -depth 24 -geometry 1280x800 :1 (code=exited, status=0/SUCCESS)
        Process: 760 ExecStartPre=/usr/bin/vncserver -kill :1 > /dev/null 2>&1 (code=exited, status=2)
       Main PID: 874 (Xtightvnc)
          Tasks: 0 (limit: 4915)
         CGroup: /system.slice/system-vncserver.slice/vncserver@1.service
                 ‣ 874 Xtightvnc :1 -desktop X -auth /home/sammy/.Xauthority -geometry 1280x800 -depth 24 -rfbwait
      
      Aug 29 19:21:10 remote-host systemd[1]: Starting Start TightVNC server at startup...
      Aug 29 19:21:10 remote-host systemd[760]: pam_unix(login:session): session opened for user sammy by (uid=0)
      Aug 29 19:21:11 remote-host systemd[848]: pam_unix(login:session): session opened for user sammy by (uid=0)
      Aug 29 19:21:12 remote-host systemd[1]: Started Start TightVNC server at startup.
      ~
      

      This finishes the VNC configuration. Remember to follow the previous steps on both remote-host and local-host. Now let’s cover the firewall configurations for each host.

      Configuring the Firewall

      Starting with the remote-host, we will configure the firewall to deny external connections to the Droplets’ services to mimic the behavior from behind NAT. In this tutorial, we are going to use port 8000 for HTTP connections, 22 for SSH, and 5901 for VNC, so we will configure the firewall to deny external connections to these ports.

      By following the initial setup for Debian 9, remote-host will have a firewall rule to allow connections to SSH. We can review this rule by running:

      The output will be the following:

      Output

      Status: active Logging: on (low) Default: deny (incoming), allow (outgoing), disabled (routed) New profiles: skip To Action From -- ------ ---- 22/tcp (OpenSSH) ALLOW IN Anywhere 22/tcp (OpenSSH (v6)) ALLOW IN Anywhere (v6)

      Remove these SSH rules to mimic the behavior behind NAT.

      Warning: Closing port 22 means you will no longer be able to use SSH to remotely log in to your server. For Droplets, this is not a problem because you can access the server’s console via the DigitalOcean Control Panel, as we did at the end of the Installing Dependencies section of this step. However, if you are not using a Droplet, be careful: closing off port 22 could lock you out of your server if you have no other means of accessing it.

      To deny SSH access, use ufw and run:

      • sudo ufw delete allow OpenSSH

      We can verify the SSH rules were removed by checking the status of the firewall again:

      The output will show no firewall rules, as in the following:

      Output

      Status: active Logging: on (low) Default: deny (incoming), allow (outgoing), disabled (routed) New profiles: skip

      Although the firewall is configured, the new configuration is not running until we enable it with:

      After enabling it, note that we won’t be able to access remote-host via SSH anymore, as mentioned in the output of the command:

      Output

      Command may disrupt existing ssh connections. Proceed with operation (y|n)? y Firewall is active and enabled on system startup

      Log out of the remote-host, then test the configuration by trying to establish an SSH or a VNC connection. It will not be possible. From now on, we may access remote-host exclusively by the DigitalOcean console.

      On local-host, we will leave the SSH ports open. We only need one firewall rule to allow access to the VNC session:

      After modifying the firewall rules, enable it by running:

      Now we may test the VNC connection using the prerequisite VNC client on your local machine to connect to local-host on port 5901 using the VNC password you’ve set up.

      To do this, open up your VNC client and connect to Local_Host_Public_IP:5901. Once you enter the password, you will connect to the VNC session.

      Note: If you have trouble connecting to the VNC session, restart the VNC service on local-host with sudo systemctl restart vncserver@1 and try to connect again.

      On its first start, Xfce will ask about the initial setup of the environment:

      Initial Xfce Configuration

      For this tutorial, select the Use default config option.

      Finally, we need to allow connections to port 80 on the front-end-server, which will be used by PageKite. Open up a terminal on front-end-server and use the following command:

      Additionally, allow traffic on port 443 for HTTPS:

      To enable the new firewall configuration, run the following:

      Now that we’ve set up the Droplets, let’s configure the PageKite front-end server.

      Step 2 — Installing PageKite on the Front-End Server

      Although it is possible to run PageKite using a Python script to set up the front-end server, it is more reliable to run it using a system service. To do so, we will need to install PageKite on the server.

      The recommended way to install a service on a Debian server is to use a distribution package. This way, it is possible to obtain automated updates and configure the service to start up on boot.

      First, we will configure the repository to install PageKite. To do that, update the package list of the repositories:

      Once the update is done, install the package dirmngr, which is necessary to support the key-ring import from the PageKite repository to ensure a secure installation:

      • sudo apt-get install dirmngr

      Next, add the repository to the /etc/apt/sources.list file, by running:

      • echo deb http://pagekite.net/pk/deb/ pagekite main | sudo tee -a /etc/apt/sources.list

      After setting up the repository, import the PageKite packaging key to our trusted set of keys, so that we can install packages from this repository. Packaging key management is done with the apt-key utility. In this case, we have to import the key AED248B1C7B2CAC3 from the key server keys.gnupg.net, which can be done by running:

      • sudo apt-key adv --recv-keys --keyserver keys.gnupg.net AED248B1C7B2CAC3

      Next, update the package lists of the repositories again, so that the pagekite package gets indexed:

      Finally, install it with:

      • sudo apt-get install pagekite

      Now that we have PageKite installed, let’s set up the front-end server and configure the service to run on boot.

      Step 3 — Configuring the Front-End Server

      The PageKite package we have just installed can be used to configure a connection to a PageKite front-end server. It can also be used to set up a front-end service to receive PageKite connections, which is what we want to do here. In order to do so, we have to edit PageKite’s configuration files.

      PageKite stores its configuration files in the directory /etc/pagekite.d. The first change we have to do is disable all lines in the /etc/pagekite.d/10_account.rc file, since this file is only used when PageKite is set up as a client to connect to a front-end server. We can edit the file using nano:

      • sudo nano /etc/pagekite.d/10_account.rc

      To disable the lines, add a # to disable the active lines of the file:

      /etc/pagekite.d/10_account.rc

      #################################[ This file is placed in the Public Domain. ]#
      # Replace the following with your account details.
      
      # kitename   = NAME.pagekite.me
      # kitesecret = YOURSECRET
      
      # Delete this line!
      # abort_not_configured
      

      After making the changes, save them and quit nano. Next, edit the file /etc/pagekite.d/20_frontends.rc:

      • sudo nano /etc/pagekite.d/20_frontends.rc

      Add the following highlighted lines to the file and comment out the defaults line, making sure to replace your_domain with the domain name you are using and examplepassword with a password of your choice:

      /etc/pagekite.d/20_frontends.rc

      
      #################################[ This file is placed in the Public Domain. ]#
      # Front-end selection
      #
      # Front-ends accept incoming requests on your behalf and forward them to
      # your PageKite, which in turn forwards them to the actual server.  You
      # probably need at least one, the service defaults will choose one for you.
      
      # Use the pagekite.net service defaults.
      # defaults
      
      # If you want to use your own, use something like:
      #     frontend = hostname:port
      # or:
      #     frontends = COUNT:dnsname:port
      
      isfrontend
      ports=80,443
      
      protos=http,https,raw
      domain=http,https,raw:*.pagekite.your_domain:examplepassword
      
      rawports=virtual
      

      Let’s explain these lines one by one. First, to configure PageKite as a front-end server, we added the line isfrontend. To configure the ports on which the server will be listening, we added ports=80,443. We also configured the protocols PageKite is going to proxy. To use HTTP, HTTPS, and RAW (which is used by SSH connections), we add the line protos=http,https,raw. We also disable the defaults settings so that there are no conflicting configurations for the server.

      Besides that, we configured the domain we are going to use for the front-end-server. For each client, a subdomain will be used, which is why we needed the DNS configurations in the Prerequisites section. We also set up a password that will be used to authenticate the clients. Using the placeholder password examplepassword, these configurations were done by adding the line domain=http,https,raw:*.pagekite.your_domain:examplepassword. Finally, we added an extra line in order to connect using SSH (which is not documented, as discussed here): rawports=virtual.

      Save the file and quit nano. Restart the PageKite service, by running:

      • sudo systemctl restart pagekite.service

      Then enable it to start on boot with:

      • sudo systemctl enable pagekite.service

      Now that we have front-end-server running, let’s test it by exposing an HTTP port on remote-host and connecting to it from local-host.

      Step 4 — Connecting to the Host Behind NAT

      To test the front-end-server, let’s start an HTTP service on remote-host and expose it to the internet using PageKite, so that we can connect to it from local-host. Remember, we have to connect to remote-host using the DigitalOcean console, since we have configured the firewall to deny incoming SSH connections.

      To start up an HTTP server for testing, we can use the Python 3 http.server module. Since Python is already installed even on the minimal Debian installation and http.server is part of the standard Python library, to start the HTTP server using port 8000 on remote-host we’ll run:

      • python3 -m http.server 8000 &

      As Debian 9 still uses Python 2 by default, it is necessary to invoke Python by running python3 to start the server. The ending & character indicates for the command to run in the background, so that we can still use the shell terminal. The output will indicate that the server is running:

      Output

      sammy@remote-host:~$ python3 -m http.server 8000 & [1] 1782 sammy@remote-host:~$ Serving HTTP on 0.0.0.0 port 8000 ...

      Note: The number 1782 that appears in this output refers to the ID that was assigned to the process started with this command and may be different depending on the run. Since it is running in the background, we can use this ID to terminate (kill) the process by issuing kill -9 1782.

      With the HTTP server running, we may establish the PageKite tunnel. A quick way to do this is by using the pagekite.py script. We can download it to remote-host running:

      • wget https://pagekite.net/pk/pagekite.py

      After downloading it, mark it as executable by running:

      Note: Since PageKite is written in Python 2 and this is the current default version of Python in Debian 9, the proceeding command works without errors. However, since default Python is being progressively migrated to Python 3 in several Linux distributions, it may be necessary to alter the first line of the pagekite.py script to set it to run with Python 2 (setting it to #!/usr/bin/python2).

      With pagekite.py available in the current directory, we can connect to front-end-server and expose the HTTP server on the domain remote-host.pagekite.your_domain by running the following, substituting your_domain and examplepassword with your own credentials:

      • ./pagekite.py --clean --frontend=pagekite.your_domain:80 --service_on=http:remote-host.pagekite.your_domain:localhost:8000:examplepassword

      Let’s take a look at the arguments in this command:

      • --clean is used to ignore the default configuration.
      • --frontend=pagekite.your_domain:80 specifies the address of our frontend. Note we are using port 80, since we have set the front end to run on this port in Step 3.
      • In the last argument, --service_on=http:remote-host.pagekite.your_domain:localhost:8000:examplepassword, we set up the service we are going to expose (http), the domain we are going to use (remote-host.pagekite.your_domain), the local address and port where the service is running (localhost:8000 since we are exposing a service on the same host we are using to connect to PageKite), and the password to connect to the frontend (examplepassword).

      Once this command is run, we will see the message Kites are flying and all is well displayed in the console. After that, we may open a browser window in the local-host VNC session and use it to access the HTTP server on remote-host by accessing the address http://remote-host.pagekite.your_domain. This will display the file system for remote-host:

      local-host Accessing remote-host Web Page

      To stop PageKite’s connection on remote-host, hit CTRL+C in the remote-host console.

      Now that we have tested front-end-server, let’s configure remote-host to make the connection with PageKite persistent and to start on boot.

      Step 5 — Making the Host Configuration Persistent

      The connection between the remote-host and the front-end-server we set up in Step 4 is not persistent, which means that the connection will not be re-established when the server is restarted. This will be a problem if you would like to use this solution long-term, so let’s make this setup persistent.

      It is possible to set up PageKite to run as a service on remote-host, so that it is started on boot. To do this, we can use the same distribution packages we used for the front-end-server in Step 3. In the remote-host console accessed through the DigitalOcean control panel, run the following command to install dirmngr:

      • sudo apt-get install dirmngr

      Then to add the PageKite repository and import the GPG key, run:

      • echo deb http://pagekite.net/pk/deb/ pagekite main | sudo tee -a /etc/apt/sources.list
      • sudo apt-key adv --recv-keys --keyserver keys.gnupg.net AED248B1C7B2CAC3

      To update the package list and install PageKite, run:

      • sudo apt-get update
      • sudo apt-get install pagekite

      To set up PageKite as a client, we will configure the front-end-server address and port in the file /etc/pagekite.d/20_frontends.rc. We can edit it using nano:

      • sudo nano /etc/pagekite.d/20_frontends.rc

      In this file, comment the line with defaults to avoid using pagekite.net service defaults. Also, configure the front-end-server address and port by using the parameter frontend, adding the line frontend = pagekite.your_domain:80 to the end of the file. Be sure to replace your_domain with the domain you are using.

      Here is the full file with the edited lines highlighted:

      /etc/pagekite.d/20_frontends.rc

      #################################[ This file is placed in the Public Domain. ]#
      # Front-end selection
      #
      # Front-ends accept incoming requests on your behalf and forward them to
      # your PageKite, which in turn forwards them to the actual server.  You
      # probably need at least one, the service defaults will choose one for you.
      
      # Use the pagekite.net service defaults.
      # defaults
      
      # If you want to use your own, use something like:
           frontend = pagekite.your_domain:80
      # or:
      #     frontends = COUNT:dnsname:port
      

      After saving the modifications and quitting nano, continue the configuration by editing the file /etc/pagekite.d/10_account.rc and setting the credentials to connect to front-end-server. First, open up the file by running:

      • sudo nano /etc/pagekite.d/10_account.rc

      To set up the domain we are going to use the domain name and the password to connect to our front-end-server, editing the parameters kitename and kitesecret respectively. We also have to comment out the last line of the file to enable the configuration, as highlighted next:

      /etc/pagekite.d/10_account.rc

      #################################[ This file is placed in the Public Domain. ]#
      # Replace the following with your account details.
      
      kitename   = remote-host.pagekite.your_domain
      kitesecret = examplepassword
      
      # Delete this line!
      # abort_not_configured
      

      Save and quit from the text editor.

      We will now configure our services that will be exposed to the internet. For HTTP and SSH services, PageKite includes sample configuration files with extensions ending in .sample in its configuration directory /etc/pagekite.d. Let’s start by copying the sample configuration file into a valid one for HTTP:

      • cd /etc/pagekite.d
      • sudo cp 80_httpd.rc.sample 80_httpd.rc

      The HTTP configuration file is almost set up. We only have to adjust the HTTP port, which we can do by editing the file we just copied:

      • sudo nano /etc/pagekite.d/80_httpd.rc

      The parameter service_on defines the address and port of the service we wish to expose. By default, it exposes localhost:80. As our HTTP server will be running on port 8000, we just have to change the port number, as highlighted next:

      /etc/pagekite.d/80_httpd.rc

      #################################[ This file is placed in the Public Domain. ]#
      # Expose the local HTTPD
      
      service_on = http:@kitename : localhost:8000 : @kitesecret
      
      # If you have TLS/SSL configured locally, uncomment this to enable end-to-end
      # TLS encryption instead of relying on the wild-card certificate at the relay.
      
      #service_on = https:@kitename : localhost:443 : @kitesecret
      
      #
      # Uncomment the following to globally DISABLE the request firewall.  Do this
      # if you are sure you know what you are doing, for more details please see
      #                <http://pagekite.net/support/security/>
      #
      #insecure
      #
      # To disable the firewall for one kite at a time, use lines like this::
      #
      #service_cfg = KITENAME.pagekite.me/80 : insecure : True
      

      Note: The service_on parameter syntax is similar to the one used with the pagekite.py script. However, the domain name we are going to use and the password are obtained from the /etc/pagekite.d/10_account.rc file and inserted by the markers @kitename and @kitesecret respectively.

      After saving the modifications to this configuration file, we have to restart the service so that the changes take effect:

      • sudo systemctl restart pagekite.service

      To start the service on boot, enable the service with:

      • sudo systemctl enable pagekite.service

      Just as we have done before, use the http.server Python module to emulate our HTTP server. It will be already running since we started it to run in the background in Step 4. However, if for some reason it is not running, we may start it again with:

      • python3 -m http.server 8000 &

      Now that we have the HTTP server and the PageKite service running, open a browser window in the local-host VNC session and use it to access remote-host by using the address http://remote-host.pagekite.your_domain. This will display the file system of remote-host in the browser.

      We have seen how to configure a PageKite front-end server and a client to expose a local HTTP server. Next, we’ll set up remote-host to expose SSH and allow remote connections.

      Step 6 — Exposing SSH with PageKite

      Besides HTTP, PageKite can be used to proxy other services, such as SSH, which is useful to access hosts remotely behind NAT in environments where it is not possible to modify networking and a router’s configurations.

      In this section, we are going to configure remote-host to expose its SSH service using PageKite, then open an SSH session from local-host.

      Just like we have done to configure HTTP with PageKite, for SSH we will copy the sample configuration file into a valid one to expose the SSH service on remote-host:

      • cd /etc/pagekite.d
      • sudo cp 80_sshd.rc.sample 80_sshd.rc

      This file is pre-configured to expose the SSH service running on port 22, which is the default configuration. Let’s take a look at its contents:

      This will show you the file:

      /etc/pagekite.d/80_sshd.rc

      #################################[ This file is placed in the Public Domain. ]#
      # Expose the local SSH daemon
      
      service_on = raw/22:@kitename : localhost:22 : @kitesecret
      

      This file is very similar to the one used to expose HTTP. The only differences are the port number, which is 22 for SSH, and the protocol, which must be set to raw when exposing SSH.

      Since we do not need to make any changes here, exit from the file.

      Restart the PageKite service:

      • sudo systemctl restart pagekite.service

      Note: We could also expose SSH using the pagekite.py script if the PageKite service wasn’t installed. We would just have to use the --service-on argument, setting the protocol to raw with the proper domain name and password. For example, to expose it using the same parameters we have configured in the PageKite service, we would use the command ./pagekite.py --clean --frontend=pagekite.your_domain:80 --service_on=raw:remote-host.pagekite.your_domain:localhost:22:examplepassword.

      On local-host, we will use the SSH client to connect to remote-host. PageKite tunnels the connections using HTTP, so that to use SSH over PageKite, we will need an HTTP proxy. There are several options of HTTP proxies we could use from the Debian repositories, such as Netcat(nc) and corkscrew. For this tutorial, we will use corkscrew, since it requires fewer arguments than nc.

      To install corkscrew on local-host, use apt-get install with the package of the same name:

      • sudo apt-get install corkscrew

      Next, generate an SSH key on local-host and append the public key to the .ssh/authorized_keys file of remote-host. To do this, follow the How to Set Up SSH Keys on Debian 9 guide, including the Copying Public Key Manually section in Step 2.

      To connect to an SSH server using a proxy, we will use ssh with the -o argument to pass in ProxyCommand and specify corkscrew as the HTTP proxy. This way, on local-host, we will run the following command to connect to remote-host through the PageKite tunnel:

      • ssh sammy@remote-host.pagekite.your_domain -i ~/id_rsa -o "ProxyCommand corkscrew %h 80 %h %p"

      Notice we provided some arguments to corkscrew. The %h and %p are tokens that the SSH client replaces by the remote host name (remote-host.pagekite.your_domain) and remote port (22, implicitly used by ssh) when it runs corkscrew. The 80 refers to the port on which PageKite is running. This port refers to the communication between the PageKite client and the front-end server.

      Once you run this command on local-host, the command line prompt for remote-host will appear.

      With our SSH connection working via PageKite, let’s next set a VNC session on remote_server and access it from local-host using VNC over SSH.

      Step 7 — Using VNC Over SSH

      Now we can access a remote host using a shell, which solves a lot of the problems that arise from servers hidden behind NAT. However, in some situations, we require access to the graphical user interface. SSH provides a way of tunneling any service in its connection, such as VNC, which can be used for graphical remote access.

      With remote-host configured to expose SSH using our front-end server, let’s use an SSH connection to tunnel VNC and have access to the remote-host graphical interface.

      Since we have already configured a VNC session to start automatically on remote-host, we will use local-host to connect to remote-host using ssh with the -L argument:

      • ssh sammy@remote-host.pagekite.your_domain -i ~/id_rsa -o "ProxyCommand corkscrew %h 80 %h %p" -L5902:localhost:5901

      The -L argument specifies that connections to a given local port should be forwarded to a remote host and port. Together with this argument, we provided a port number followed by a colon, then an IP address, domain, or host name, followed by another colon and a port number. Let’s take a look at this information in detail:

      • The first port number refers to the one we are going to use on the host that is starting the SSH connection (in this case local-host), to receive the tunneled connection from the remote host. In this case, from the point of view of local-host, the VNC Session from remote-host will be available locally, on port 5902. We could not use the port 5901 since it is already being used on local-host for its own VNC session.
      • After the first colon, we provide the host name (or IP address) of the device that is serving the VNC session we wish to tunnel. If we provide a host name, it will be resolved into an IP address by the host that is serving SSH. In this case, since remote-host is serving the SSH connection and the VNC session is also served by this same host, we can use localhost.
      • After the second colon, we provide the port in which the service to be tunneled is served. We use port 5901, since VNC is running on this port on the remote-host.

      After the connection is established, we will be presented with a remote shell on remote-host.

      Now we can reach the remote-host VNC session from local-host by connecting to port 5902 itself. To do so, open a shell from the local-host GUI in your VNC client, then run:

      Upon providing the remote-host VNC password, we will be able to access its graphical environment.

      Note: If the VNC session has been running for too long, you may encounter an error in which the GUI on remote-host is replaced by a gray screen with an X for a cursor. If this happens, try restarting the VNC session on remote-host with sudo systemctl restart vncserver@1. Once the service is running, try connecting again.

      This setup can be useful for support teams using remote access. It is possible to use SSH to tunnel any service that can be reached by remote-host. This way, we could set up remote-host as a gateway to a local attached network with many hosts, including some running Windows or another OS. As long as the hosts have a VNC server with a VNC session set up, it would be possible to access them with a graphical user interface through SSH tunneled by our PageKite front-end-server.

      In the final step, we will configure the PageKite frontend to support more clients with different passwords.

      Step 8 — Configuring the Front-End Server for Many Clients (Optional)

      Suppose we are going to use our front-end-server to offer remote access to many clients. In this multi-user setup, it would be a best practice to isolate them, using a different domain name and password for each one to connect to our server. One way of doing this is by running several PageKite services on our server on different ports, each one configured with its own subdomain and password, but this can be difficult to keep organized.

      Fortunately, the PageKite frontend supports the configuration of multiple clients itself, so that we can use the same service on a single port. To do this, we would configure the front end with the domain names and passwords.

      As we have configured the wildcard DNS entry *.pagekite.your_domain pointing out to our front-end-server, DNS entries in subdomains like remote-host.client-1.pagekite.your_domain can also point out to our server, so that we could use domains ending in client1.pagekite.your_domain and client2.pagekite.your_domain to identify hosts of different clients with different passwords.

      To do this on the front-end-server, open the /etc/pagekite.d/20_frontends.rc file:

      • sudo nano /etc/pagekite.d/20_frontends.rc

      Add the domains using the domain keyword and set different passwords for each one. To set up the domains we’ve mentioned, add:

      /etc/pagekite.d/20_frontends.rc

      #################################[ This file is placed in the Public Domain. ]#
      # Front-end selection
      #
      # Front-ends accept incoming requests on your behalf and forward them to
      # your PageKite, which in turn forwards them to the actual server.  You
      # probably need at least one, the service defaults will choose one for you.
      
      # Use the pagekite.net service defaults.
      # defaults
      
      # If you want to use your own, use something like:
      #     frontend = hostname:port
      # or:
      #     frontends = COUNT:dnsname:port
      
      isfrontend
      ports=80,443
      
      protos=http,https,raw
      domain=http,https,raw:*.pagekite.your_domain:examplepassword
      domain=http,https,raw:*.client-1.pagekite.your_domain:examplepassword2
      domain=http,https,raw:*.client-2.pagekite.your_domain:examplepassword3
      
      rawports=virtual
      

      Save and exit the file.

      After modifying the configuration files, restart PageKite:

      • sudo systemctl restart pagekite.service

      On the remote hosts, let’s configure the PageKite client to connect according to the new domains and passwords. For example, in remote-host, to connect using client-1.pagekite.your_domain, modify the file /etc/pagekite.d/10_account.rc, where the credentials to connect to front-end-server are stored:

      • sudo nano /etc/pagekite.d/10_account.rc

      Change kitename and kitesecret to the appropriate credentials. For the domain remote-host.client-1.pagekite.your_domain, the configuration would be:

      /etc/pagekite.d/10_account.rc

      #################################[ This file is placed in the Public Domain. ]#
      # Replace the following with your account details.
      
      kitename   = remote-host.client-1.pagekite.your_domain
      kitesecret = examplepassword2
      
      # Delete this line!
      
      

      Save and exit the file.

      After modifying the file, restart the PageKite service:

      • sudo systemctl restart pagekite.service

      Now, on local-host, we can connect to remote-host via SSH with:

      • ssh sammy@remote-host.client-1.pagekite.your_domain -i ~/id_rsa -o "ProxyCommand corkscrew %h 80 %h %p"

      We could use the domain client-2.pagekite.your-domain for another client. This way, we could administrate the services in an isolated way, with the possibility to change the password of one client or even disable one of them without affecting the other.

      Conclusion

      In this article, we set up a private PageKite front-end server on a Debian 9 Droplet and used it to expose HTTP and SSH services on a remote host behind NAT. We then connected to these services from a local-host server and verified the PageKite functionality. As we have mentioned, this could be an effective setup for remote access applications, since we can tunnel other services in the SSH connection, such as VNC.

      If you’d like to learn more about PageKite, check out the PageKite Support Info. If you would like to dive deeper into networking with Droplets, take a look through DigitalOcean’s Networking Documentation.



      Source link

      7 Web Design Mistakes That Could Be Scaring Away Your Visitors


      What is keeping internet users up at night? It could be that scary movie they just watched, or worse yet, it could be your website.

      If you’ve got a digital presence decked out in a ghastly design, it’s likely robbing you of precious clicks, driving away potential customers, and sabotaging your chances of building a well-ranking, authoritative brand.

      The good news? You don’t need to be a web designer or an expert in web development to improve a weak website!

      In this guide, we’ll detail the seven design mistakes that could be scaring away your visitors — and how to fix them quickly, so you can make sure your website is all treats and no tricks.

      Professional Website Design Made Easy

      Make your site stand out with a professional design from our partners at RipeConcepts. Packages start at $299.

      1. Scary Slow Site Speeds

      For consumers browsing the internet, nothing is more spine-chilling than a slow-loading site. Before you can say “Boo!” they’ve abandoned your page, leaving you with irritated audiences and suffering engagement.

      In fact, page speed can be the make-it-or-break-it factor for the success (or failure) of your website, dramatically affecting everything from sales to sign-ups to search traffic.

      Visitors have high expectations: 47% of consumers expect a web page to load in two seconds or less, and 49% abandon a site that takes more than three seconds to load. Plus, 79% of online shoppers who have trouble with site performance say they won’t return to the site to buy again.

      Ouch. That’s a lot riding on your site’s load time and overall performance.

      Is your site (creepy) crawling? Use a tool like Google’s PageSpeed Insights to clue into your site’s performance and discover needed areas of improvement to achieve lightning-fast load times.

      Woman typing on computer

      We’ve also got a few tips to speed things up: trim down bulky code, patch up caching issues, optimize your site’s CSS, and perhaps most importantly, choose a quality web host.

      2. A Haunting User Experience

      Think about the last experience you had at a brick-and-mortar store. Did the floor layout, decor, and atmosphere invite and engage you? Or did tight aisles, overly-intrusive sensory elements or a labyrinth-like setup negatively affect your shopping experience? Whatever your experience, we’ll take a guess that it will largely determine whether you visit that store again.

      It’s the same with your website.

      The kind of experience users have on your site (from the second they land there) will affect how — and if — they engage with you. If your site contains nightmare navigation, unsavory design elements, or poor performance, visitors won’t stay long.

      Open laptop on table

      To craft and cultivate a positive user experience, make your navigation and drop-down menu intuitive so users can find what they’re looking for. Then follow aesthetically-pleasing design principles and keep site operations fine-tuned. If your visitors have an enjoyable experience on your site, not only will they be more likely to come back, they’ll engage with you and help you find site success.

      3. An Unresponsive Layout

      Ready for some hair-raising facts? Based on data from January 2018, the global population of unique mobile users reached 3.7 billion — yep, billion. What’s more, 52% of web internet traffic in 2018 was mobile, and the mobile-only audience is expected to grow to 55 million by 2022.

      Mobile internet usage is gaining significant traction, and will likely overtake desktop internet usage in the future. That means if your website is not optimized for different types of screens, including smartphones and tablets, you’re going to lose out on meaningful engagement from your target audiences.

      Plus, having a responsive site isn’t just important for user experience; it’s a critical element of your SEO strategy. Google operates on a mobile-first indexing policy, meaning the search engine predominantly uses the mobile version of website content for indexing and ranking. So, investing in a mobile-first approach to your web design is essential when it comes to optimizing for search engines and, ultimately, driving traffic to your site.

      Hands holding iPhone on black table

      Responsive design also affects your brand image. A whopping 89% of people are likely to recommend a brand after a positive experience on mobile. On the other hand, 46% of people say they would not buy from a brand again if they had a less-than-stellar mobile experience. What’s more, 57% of users say they won’t recommend a business with a poorly-designed mobile site.

      The key takeaway here?  Prioritize a website optimized for mobile and responsive across devices and different browsers.

      4. Terrifying Typography

      You might not think the fonts on your site matter, but utilizing type haphazardly or without thoughtful intention is a major design flaw and affects the experience your visitors will have, even if just subconsciously.

      Just like with other elements of design, typography follows rules — dictating what text combinations, colors, font size, and layouts are aesthetically-pleasing and effective. Well-established typography can increase your conversion rates, build your brand authority, encourage action, amplify your message, and create a positive sensory experience. Consult our guide for typography to-dos, and plan your font strategy with meticulous consideration or else you risk interrupting usability and cognitive fluency.

      5. Ghostly Calls to Action

      When you enter a haunted house, you never quite know what you’re going to get. Zombie up ahead? Hidden skeletons behind the door? Unidentified noises behind you? As you’re feeling for the exit in the dark, you’re going to face a host of unexpected and spooky mysteries.

      Your website visitors shouldn’t feel like they’re having a haunted house experience when they enter your URL.

      When internet users land on your site, it should be free of mystery ghosts and ghouls. Meaning, visitors should know what to expect. They should know where to find a contact button, how to navigate your menu, and above all, what you want them to do — whether that’s read a blog post, subscribe to an email list, follow your social media platforms, or purchase a specific product.

      Product page on website with Add to Cart button

      Having a clear call-to-action button helps users know how to engage with you, vastly increasing the chances you’ll find success (and those boosted analytics you want!). Guide potential customers to a specific action with a clear, prominent, and well-distinguished icon or button and include it on all your pages and content.

      6. Spine-Chilling Safety Oversights

      Online users worry (a lot) about online safety. In fact, 73% of Americans who use the web are concerned about online privacy, so your website needs to be a safe space, free from creepy-crawly web demons and malicious malware.

      Person holding credit card near laptop

      First, outfit your site with an SSL certificate, giving your visitors the peace of mind that your website is secure. Even the presence of a safety badge or security can do wonders. Then, tighten security by using a quality web host, upgrading to HTTPS, utilizing secure plugins, configuring file permissions, and backing up your site regularly to keep site terrors at bay.

      7. CloakandDagger Content

      The fact of the matter is, you need to offer your website visitors value or else they have no reason to stay.

      With quality content, you provide users with a way to engage with you, helping drive traffic and build a following. But if your content is hard to find, sub-par, or (gasp!) nonexistent, you’re sabotaging your chances of success. Use the following tips to make sure you’re creating top-notch content.

      Embrace Your Niche

      Whatever field or industry you occupy, keep your content consistent for your target audience. Relevant content will build your brand and help establish your site as an authority amongst your competition, distinguishing you from the rest.

      Proofread

      Error-ridden content will brand you as an amateur. Stay professional by taking the time to edit and polish your content before hitting “Submit.” Clean content will go a long way toward establishing your brand.

      Offer Value

      Not only are attention spans shorter than ever, but often, visitors need a good reason to even engage with your site. Entice potential customers with incentives — whether that be free e-books, blog posts, printables, or insider tips — to get their eyes on your content.

      Person typing blog post on laptop

      Prioritize Consistency

      You don’t have to post content on your site every hour, but you should be posting regularly. Your visitors should know when to expect new content from you. This will build their trust — and your authority. Use a content calendar to plan, schedule posts ahead of time, and keep yourself organized.

      Fix Those Eerie Web Design Errors

      Don’t give your audiences the heebie-jeebies with poor website design. Offer them the best of online experiences with the virtual treat (we’re talking king-size candy bars here) of a well-designed website.

      Fixing those pesky web design mistakes will prime you for top-of-the-line placement in search engines, improve user experience, boost conversions, enhance site usability, lower your site’s bounce rate, and establish your online presence — plus, they won’t leave screaming.

      Sounds good, right?

      If you’re ready to makeover your website, trick-or-treat yo’ self to professional web design. We’ve partnered with RipeConcepts, one of the world’s leading web design firms, to make your dream site fast, easy, and affordable. Sign up for a free consultation today!



      Source link