diff --git a/.dockerignore b/.dockerignore
index 21fafffb..c5288418 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1 +1,3 @@
-/**/node_modules
\ No newline at end of file
+/**/node_modules
+/logs
+/devU-api/src/tango/tests/test_files
\ No newline at end of file
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..e241fc4a
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,6 @@
+# Configure line ending normalisation for this repository.
+# See http://schacon.github.io/git/gitattributes.html for more information.
+# or https://docs.github.com/en/get-started/getting-started-with-git/configuring-git-to-handle-line-endings
+
+# Shell scripts require LF
+*.sh    text eol=lf
diff --git a/.gitignore b/.gitignore
index 37fdfd5f..e0c4dad5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@ node_modules/
 ./docker-compose.yml
 /.idea
 /.vscode
+/logs
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
index e69de29b..6730f75f 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "tango"]
+	path = tango
+	url = https://github.com/UB-CSE-IT/Tango
diff --git a/devU-api/package.json b/devU-api/package.json
index 72adbf9d..647ccabe 100644
--- a/devU-api/package.json
+++ b/devU-api/package.json
@@ -13,7 +13,8 @@
     "format": "prettier --write \"./**/*.{js,ts,json,md}\"",
     "pre-commit": "lint-staged",
     "generate-config": "docker run --pull always -v $(pwd)/config:/config --user $(id -u):$(id -g) --rm ubautograding/devtools /generateConfig.sh config/default.yml",
-    "populate-db": "ts-node-dev ./scripts/populate-db.ts"
+    "populate-db": "ts-node-dev ./scripts/populate-db.ts",
+    "tango":  "ts-node-dev ./src/tango/tests/tango.endpoint.test.ts"
   },
   "lint-staged": {
     "./**/*.{js,ts,json,md}": [
diff --git a/devU-api/src/environment.ts b/devU-api/src/environment.ts
index 980ce5a7..d90efe0a 100644
--- a/devU-api/src/environment.ts
+++ b/devU-api/src/environment.ts
@@ -51,6 +51,9 @@ const refreshTokenBuffer = load('auth.jwt.refreshTokenExpirationBufferSeconds')
 // if it is undefined it is running on dev machine
 const isDocker = !(process.env.dev === undefined)
 
+if (process.env.TANGO_KEY === undefined){
+  throw Error('Tango key not found.\nMake sure to set environment variable TANGO_KEY in the api service in docker-compose')
+}
 
 const environment = {
   port,
diff --git a/devU-api/src/tango/tango.service.ts b/devU-api/src/tango/tango.service.ts
new file mode 100644
index 00000000..59c335fe
--- /dev/null
+++ b/devU-api/src/tango/tango.service.ts
@@ -0,0 +1,105 @@
+import './tango.types'
+
+const tangoHost = `http://${(process.env.TANGO_KEY ?? 'localhost:3000')}`
+const tangoKey = process.env.TANGO_KEY ?? 'test'
+
+// for more info https://docs.autolabproject.com/tango-rest/
+
+/**
+ * Opens a directory for a given course and lab.
+ * @param courselab - The combination of the course name and the lab name.
+ */
+export async function openDirectory(courselab: string): Promise<OpenResponse | null> {
+  const url = `${tangoHost}/open/${tangoKey}/${courselab}/`
+  const response = await fetch(url, { method: 'GET' })
+  return response.ok ? await response.json() : null
+}
+
+/**
+ * Uploads a file to the server for a given course and lab.
+ * @param courselab - The combination of the course name and the lab name.
+ * @param fileName - The file name, used to identify the file when uploaded
+ * @param file - The file to be uploaded.
+ */
+export async function uploadFile(courselab: string, file: File, fileName: string): Promise<UploadResponse | null> {
+  const url = `${tangoHost}/upload/${tangoKey}/${courselab}/`
+  const formData = new FormData()
+  formData.append('file', file)
+  const response = await fetch(url, { method: 'POST', body: formData, headers: { 'filename': fileName } })
+  return response.ok ? await response.json() : null
+}
+
+/**
+ * Adds a job to the server for a given course and lab.
+ * @param courselab - The combination of the course name and the lab name.
+ * @param job - The job request object.
+ */
+export async function addJob(courselab: string, job: AddJobRequest): Promise<AddJobResponse | null> {
+  const url = `${tangoHost}/addJob/${tangoKey}/${courselab}/`
+  const response = await fetch(url, {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify(job),
+  })
+  return response.ok ? await response.json() : null
+}
+
+/**
+ * Polls the server for the status of a job.
+ * @param courselab - The combination of the course name and the lab name.
+ * @param outputFile - The name of the output file.
+ */
+export async function pollJob(courselab: string, outputFile: string): Promise<PollSuccessResponse | PollFailureResponse> {
+  const url = `${tangoHost}/poll/${tangoKey}/${courselab}/${outputFile}/`
+  const response = await fetch(url, { method: 'GET' })
+  const data = await response.json()
+  return response.ok ?
+    data as PollSuccessResponse
+    : data as PollFailureResponse
+}
+
+/**
+ * Retrieves information about the Tango service.
+ */
+export async function getInfo(): Promise<InfoResponse | null> {
+  const url = `${tangoHost}/info/${tangoKey}/`
+  const response = await fetch(url, { method: 'GET' })
+  return response.ok ? await response.json() : null
+}
+
+/**
+ * Retrieves information about the pool of instances for a given image.
+ * @param image - The name of the image.
+ */
+export async function getPoolInfo(image: string): Promise<Object | null> {
+  const url = `${tangoHost}/pool/${tangoKey}/${image}/`
+  const response = await fetch(url, { method: 'GET' })
+  return response.ok ? await response.json() : null
+}
+
+/**
+ * Pre-allocates a pool of instances for a given image.
+ * @param image - The name of the image.
+ * @param num - The number of instances to pre-allocate.
+ * @param request - The request object.
+ */
+export async function preallocateInstances(image: string, num: number, request: PreallocRequest): Promise<PreallocResponse | null> {
+  const url = `${tangoHost}/prealloc/${tangoKey}/${image}/${num}/`
+  const response = await fetch(url, {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify(request),
+  })
+  return response.ok ? await response.json() : null
+}
+
+/**
+ * Retrieves information about jobs.
+ * @param deadjobs - A flag to indicate whether to retrieve dead jobs (1) or running jobs (0).
+ * @returns Empty response on successful request
+ */
+export async function getJobs(deadjobs: number): Promise<{} | null> {
+  const url = `${tangoHost}/jobs/${tangoKey}/${deadjobs}/`
+  const response = await fetch(url, { method: 'POST' })
+  return response.ok ? {} : null
+}
\ No newline at end of file
diff --git a/devU-api/src/tango/tango.types.ts b/devU-api/src/tango/tango.types.ts
new file mode 100644
index 00000000..04c0434d
--- /dev/null
+++ b/devU-api/src/tango/tango.types.ts
@@ -0,0 +1,104 @@
+/**
+ * Represents the response body for the `/open` request.
+ */
+interface OpenResponse {
+  statusMsg: string;
+  statusId: number;
+  files: { [fileName: string]: string };
+}
+
+/**
+ * Represents the response body for the `/upload` request.
+ */
+interface UploadResponse {
+  statusMsg: string;
+  statusId: number;
+}
+
+/**
+ * Represents a single file information object with `localFile` and `destFile` properties.
+ */
+interface FileInfo {
+  localFile: string;
+  destFile: string;
+}
+
+/**
+ * Represents the request body for the `/addJob` request.
+ */
+interface AddJobRequest {
+  image: string;
+  files: FileInfo[];
+  jobName: string;
+  output_file: string;
+  timeout?: number;
+  max_kb?: number;
+  callback_url?: string;
+}
+
+/**
+ * Represents the response body for the `/addJob` request.
+ */
+interface AddJobResponse {
+  statusMsg: string;
+  statusId: number;
+  jobId: number;
+}
+
+/**
+ * Represents the response body for the `/poll` request when the job is completed.
+ */
+interface PollSuccessResponse {
+  // The autograder output file content ???
+}
+
+/**
+ * Represents the response body for the `/poll` request when the job is not completed.
+ */
+interface PollFailureResponse {
+  statusMsg: string;
+  statusId: number;
+}
+
+/**
+ * Represents the response body for the `/info` request.
+ */
+interface InfoResponse {
+  info: {
+    num_threads: number;
+    job_requests: number;
+    waitvm_timeouts: number;
+    runjob_timeouts: number;
+    elapsed_secs: number;
+    runjob_errors: number;
+    job_retries: number;
+    copyin_errors: number;
+    copyout_errors: number;
+  };
+  statusMsg: string;
+  statusId: number;
+}
+
+
+/**
+ * Represents the request body for the `/prealloc` request.
+ */
+interface PreallocRequest {
+  vmms: string;
+  cores: number;
+  memory: number;
+}
+
+/**
+ * Represents the response body for the `/prealloc` request.
+ */
+interface PreallocResponse {
+  status: string;
+}
+
+/**
+ * Represents the request body for the `/jobs` request.
+ */
+interface JobsRequest {
+  deadjobs: number;
+}
diff --git a/devU-api/src/tango/tests/tango.endpoint.test.ts b/devU-api/src/tango/tests/tango.endpoint.test.ts
new file mode 100644
index 00000000..5cc24f56
--- /dev/null
+++ b/devU-api/src/tango/tests/tango.endpoint.test.ts
@@ -0,0 +1,11 @@
+import { getInfo, preallocateInstances } from '../tango.service'
+
+async function main(){
+  const res = await getInfo()
+  const pre = preallocateInstances()
+  console.log(res)
+}
+
+main().then(value => {
+  console.log('main complete')
+})
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
index ca495310..e6519ed1 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -12,7 +12,7 @@ services:
     expose:
       - '5432'
   config:
-#     Generates a config file for the API
+    #     Generates a config file for the API
     build:
       context: devU-api/scripts
     command: "./generateConfig.sh config/default.yml"
@@ -25,6 +25,7 @@ services:
       context: .
       dockerfile: api.Dockerfile
     environment:
+      TANGO_KEY: devutangokey # todo load in from env file. for now this is defined in tango section below
       WAIT_HOSTS: db:5432
       dev: 0 # value here is irrelevant; just here to make sure dev env exists
     depends_on:
@@ -63,3 +64,43 @@ services:
       MINIO_ROOT_USER: typescript_user
       MINIO_ROOT_PASSWORD: changeMe
     command: server /data --console-address ":9001"
+  # tango stuff
+  redis:
+    container_name: redis
+    image: redis:latest
+    ports:
+      - '127.0.0.1:6379:6379'
+    deploy:
+      replicas: 1
+    restart: unless-stopped
+  tango:
+    container_name: tango
+    ports:
+      - '127.0.0.1:3000:3000'
+    build:
+      context: ./tango
+    environment:
+      - DOCKER_REDIS_HOSTNAME=redis
+      - RESTFUL_KEY=devutangokey
+#      - DOCKER_DEPLOYMENT
+      # Path to volumes within the Tango container. Does not need to be modified.
+#      - DOCKER_VOLUME_PATH
+      # Modify the below to be the path to volumes on your host machine
+#      - DOCKER_TANGO_HOST_VOLUME_PATH
+
+    depends_on:
+      - redis
+    volumes:
+      - ./tango.config.py:/opt/TangoService/Tango/config.py
+      - /var/run/docker.sock:/var/run/docker.sock
+      - ./logs/tango/:/var/log/tango/
+      - ./logs/tangonginx:/var/log/nginx
+#      - ./Tango/volumes:/opt/TangoService/Tango/volumes
+
+#  certbot:
+#    container_name: certbot
+#    image: certbot/certbot
+#    volumes:
+#      - ./ssl/certbot/conf:/etc/letsencrypt
+#      - ./ssl/certbot/www:/var/www/certbot
+    restart: unless-stopped
diff --git a/readme.md b/readme.md
index aab3da89..f5599024 100644
--- a/readme.md
+++ b/readme.md
@@ -45,6 +45,18 @@ systems or tool versions. The installation varies depending on your operating sy
 their website.
 
 ### 2. Clone this repo to your computer.
+
+*Note for Windows users*
+
+You have to run the following commands, this repo contains shell scripts that have LF line endings. 
+By default, git will convert them CRLF on windows this will cause the script to not run
+
+The command below will configure git to not change line endings for the files in the repo
+
+```
+ git config --global core.autocrlf input 
+```
+
 Open a terminal.
 * On Windows, press `Windows + R` to open a run prompt, type "`cmd`", and press enter.
 * On MacOS, open the Launchpad (on your Dock) and launch "Terminal".
diff --git a/tango b/tango
new file mode 160000
index 00000000..08d93039
--- /dev/null
+++ b/tango
@@ -0,0 +1 @@
+Subproject commit 08d930394ee42478b55351b64818382de0d708a0
diff --git a/tango.config.py b/tango.config.py
new file mode 100644
index 00000000..d2d7de2a
--- /dev/null
+++ b/tango.config.py
@@ -0,0 +1,155 @@
+#
+# config.py - Global configuration constants and runtime info
+#
+
+import logging
+import time
+
+# Config - defines
+import os
+
+
+class Config(object):
+    #####
+    # Part 1: Tango constants for developers
+    #
+    # These allow developers to run test versions of Tango on the same
+    # server as the production Tango
+
+    # Unique prefix that defines VM name space for this Tango
+    # version. When working in development, this prefix should be your
+    # unique identifier. The "prod" prefix is reserved for production
+    DEFAULT_PREFIX = "local"
+    PREFIX = os.getenv("DOCKER_DEPLOYMENT", DEFAULT_PREFIX).lower()
+
+    # Default port for the RESTful server to listen on.
+    PORT = 3000
+
+    # Log file. Setting this to None sends the server output to stdout
+    LOGFILE = None
+
+    # Logging level
+    LOGLEVEL = logging.DEBUG
+
+    # Courselabs directory. Must be created before starting Tango
+    COURSELABS = "courselabs"
+
+    # Directory within each courselab where Tango will copy the output
+    # for jobs of that courselab
+    OUTPUT_FOLDER = "output"
+
+    # VMMS to use. Must be set to a VMMS implemented in vmms/ before
+    # starting Tango.  Options are: "localDocker", "distDocker",
+    # "tashiSSH", and "ec2SSH"
+    VMMS_NAME = "localDocker"
+
+    # Update this to the 'volumes' directory of your Tango installation if
+    # Docker is being used as the VMMs.
+    # It must be an absolute path with trailing slash, i.e
+    # /opt/TangoService/Tango/volumes/
+    DEFAULT_DOCKER_VOLUME_PATH = "/opt/TangoService/Tango/volumes/"
+    DOCKER_VOLUME_PATH = os.getenv("DOCKER_VOLUME_PATH", DEFAULT_DOCKER_VOLUME_PATH)
+
+    #####
+    # Part 2: Constants that shouldn't need to change very often.
+    #
+
+    # Keys for Tango to authenticate client requests
+    DEFAULT_KEY = "test"
+    KEYS = [os.getenv("RESTFUL_KEY", DEFAULT_KEY)]
+
+    # Queue manager checks for new work every so many seconds
+    DISPATCH_PERIOD = 0.2
+
+    # Timer polling interval used by timeout() function
+    TIMER_POLL_INTERVAL = 1
+
+    # Number of server threads
+    NUM_THREADS = 20
+
+    # We have the option to reuse VMs or discard them after each use
+    REUSE_VMS = True
+
+    # Worker waits this many seconds for functions waitvm, copyin (per
+    # file), runjob, and copyout (per file) functions to finish.
+    INITIALIZEVM_TIMEOUT = 180
+    WAITVM_TIMEOUT = 60
+    COPYIN_TIMEOUT = 30
+    RUNJOB_TIMEOUT = 60
+    COPYOUT_TIMEOUT = 30
+
+    # Docker constants
+    BOOT2DOCKER_INIT_TIMEOUT = 5
+    BOOT2DOCKER_START_TIMEOUT = 30
+    BOOT2DOCKER_ENV_TIMEOUT = 5
+    DOCKER_IMAGE_BUILD_TIMEOUT = 300
+    DOCKER_RM_TIMEOUT = 5
+    DOCKER_HOST_USER = ""
+
+    # Maximum size for input files in bytes
+    MAX_INPUT_FILE_SIZE = 250 * 1024 * 1024  # 250MB
+
+    # Maximum size for output file in bytes
+    MAX_OUTPUT_FILE_SIZE = 1000 * 1024
+
+    # VM ulimit values
+    VM_ULIMIT_FILE_SIZE = 100 * 1024 * 1024
+    VM_ULIMIT_USER_PROC = 100
+
+    # How many times to reschedule a failed job
+    JOB_RETRIES = 2
+
+    # How many times to attempt an SSH connection
+    SSH_RETRIES = 5
+
+    # Frequency of retrying SSH connections (in seconds)
+    SSH_INTERVAL = 0.5
+
+    # Give VMMS this many seconds to destroy a VM before giving up
+    DESTROY_SECS = 5
+
+    # Time to wait between creating VM instances to give DNS time to cool down
+    CREATEVM_SECS = 1
+
+    # Default vm pool size
+    POOL_SIZE = 2
+
+    # Optionally log finer-grained timing information
+    LOG_TIMING = False
+
+    # Largest job ID
+    MAX_JOBID = 1000
+
+    ######
+    # Part 3: Runtime info that you can retrieve using the /info route
+    #
+    start_time = time.time()
+    job_requests = 0
+    job_retries = 0
+    waitvm_timeouts = 0
+    copyin_errors = 0
+    runjob_timeouts = 0
+    runjob_errors = 0
+    copyout_errors = 0
+
+    ######
+    # Part 4: Settings for shared memory
+    #
+    USE_REDIS = True
+    DEFAULT_REDIS_HOSTNAME = "127.0.0.1"
+    REDIS_HOSTNAME = os.getenv("DOCKER_REDIS_HOSTNAME", DEFAULT_REDIS_HOSTNAME).lower()
+    REDIS_PORT = 6379
+
+    ######
+    # Part 5: EC2 Constants
+    #
+    EC2_REGION = ""
+    EC2_USER_NAME = ""
+    DEFAULT_AMI = ""
+    DEFAULT_INST_TYPE = ""
+    DEFAULT_SECURITY_GROUP = ""
+    SECURITY_KEY_PATH = ""
+    DYNAMIC_SECURITY_KEY_PATH = ""
+    SECURITY_KEY_NAME = ""
+    TANGO_RESERVATION_ID = ""
+    INSTANCE_RUNNING = 16  # Status code of a instance that is running
