├── .dockerignore ├── .github └── workflows │ ├── build.yml │ ├── compute.yml │ └── stale.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── assets └── docker-hub-dataeditors.png ├── build.sh ├── code └── main.do ├── config.txt ├── data └── README.md ├── init.config.txt ├── run.sh └── setup.do /.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore those not relevant for this version 2 | bin-exclude/stata-installed-12.* 3 | bin-exclude/stata-installed-13.* 4 | bin-exclude/stata-installed-14.* 5 | bin-exclude/stata-installed-15.* 6 | bin-exclude/stata-installed-16.* 7 | # bin-exclude/stata-installed-17.* 8 | bin-exclude/stata-png-fix.tgz 9 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build docker image 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | paths: 8 | - 'Dockerfile' 9 | - 'build.sh' 10 | - 'build.yml' 11 | - 'setup.do' 12 | workflow_dispatch: 13 | 14 | jobs: 15 | docker: 16 | runs-on: ubuntu-latest 17 | env: 18 | # This needs to be adapted to your own PROJECT!! 19 | # It may not correspond to your Docker login 20 | # DOCKER_PROJECT: ${{ secrets.DOCKERHUB_USERNAME }} 21 | DOCKER_PROJECT: aeadataeditor 22 | steps: 23 | - 24 | name: Checkout 25 | uses: actions/checkout@v2 26 | - 27 | name: Create license 28 | run: | 29 | echo "${{ secrets.STATA_LIC_BASE64 }}" | base64 -d > stata.lic 30 | ls -l stata.lic 31 | rm config.txt 32 | - 33 | name: Set up Docker Buildx 34 | uses: docker/setup-buildx-action@v1 35 | 36 | - name: Sanitize repo slug 37 | uses: actions/github-script@v4 38 | id: repo_slug 39 | with: 40 | result-encoding: string 41 | script: return '${{ github.event.repository.name }}'.toLowerCase() 42 | 43 | - 44 | name: Login to DockerHub 45 | uses: docker/login-action@v1 46 | with: 47 | username: ${{ secrets.DOCKERHUB_USERNAME }} 48 | password: ${{ secrets.DOCKERHUB_TOKEN }} 49 | - 50 | name: Build and push 51 | uses: docker/build-push-action@v2 52 | with: 53 | context: . 54 | push: true 55 | tags: ${{ env.DOCKER_PROJECT }}/${{ steps.repo_slug.outputs.result }}:latest 56 | secret-files: | 57 | "statalic=./stata.lic" 58 | -------------------------------------------------------------------------------- /.github/workflows/compute.yml: -------------------------------------------------------------------------------- 1 | name: Compute analysis 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | jobs: 10 | compute: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - 14 | name: Checkout 15 | uses: actions/checkout@v2 16 | # Workaround - we create a local license file 17 | - name: Create license 18 | run: | 19 | echo "${{ secrets.STATA_LIC_BASE64 }}" | base64 -d > stata.lic 20 | ls -l stata.lic 21 | # we want a lower-case version of the repo name 22 | - name: Sanitize repo slug 23 | uses: actions/github-script@v4 24 | id: repo_slug 25 | with: 26 | result-encoding: string 27 | script: return '${{ github.repository }}'.toLowerCase() 28 | # Compute the main result, re-using the run.sh script 29 | - name: Compute 30 | run: "bash -x ./run.sh ./stata.lic" 31 | # send it to the results branch 32 | - name: Deploy 33 | uses: peaceiris/actions-gh-pages@v3.8.0 34 | with: 35 | github_token: ${{ secrets.GITHUB_TOKEN }} 36 | publish_dir: . 37 | user_name: 'Github Action Bot' 38 | user_email: 'lars.vilhuber@cornell.edu' 39 | publish_branch: results 40 | keep_files: true 41 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Mark stale issues and pull requests 2 | 3 | on: 4 | schedule: 5 | - cron: '22 1 * * *' 6 | 7 | jobs: 8 | stale: 9 | 10 | runs-on: ubuntu-latest 11 | permissions: 12 | issues: write 13 | pull-requests: write 14 | 15 | steps: 16 | - uses: actions/stale@v3 17 | with: 18 | repo-token: ${{ secrets.GITHUB_TOKEN }} 19 | stale-issue-message: 'Stale issue message' 20 | stale-pr-message: 'Stale pull request message' 21 | stale-issue-label: 'no-issue-activity' 22 | stale-pr-label: 'no-pr-activity' 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | stata.lic* 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1.2 2 | ARG SRCVERSION=17 3 | ARG SRCTAG=2022-01-17 4 | ARG SRCHUBID=dataeditors 5 | 6 | FROM ${SRCHUBID}/stata${SRCVERSION}:${SRCTAG} 7 | 8 | # install any packages into the home directory as the user, 9 | USER statauser 10 | COPY setup.do /setup.do 11 | WORKDIR /home/statauser 12 | 13 | # copy the license in so we can do the install of packages 14 | USER root 15 | RUN --mount=type=secret,id=statalic \ 16 | cp /run/secrets/statalic /usr/local/stata/stata.lic \ 17 | && chmod a+r /usr/local/stata/stata.lic \ 18 | && /usr/local/stata/stata do /setup.do | tee setup.$(date +%F).log \ 19 | && rm /usr/local/stata/stata.lic 20 | 21 | # Setup for standard operation 22 | USER statauser 23 | VOLUME /project 24 | WORKDIR /project 25 | ENTRYPOINT ["stata-mp"] 26 | 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The Stata binary code is (c) 2021 Stata and a license must be purchased. https://stata.com 2 | 3 | The Dockerfile and any containers are under a BSD 3-Clause License. 4 | 5 | ==== 6 | 7 | BSD 3-Clause License 8 | 9 | Copyright (c) 2021, AEA Data Editor 10 | All rights reserved. 11 | 12 | Redistribution and use in source and binary forms, with or without 13 | modification, are permitted provided that the following conditions are met: 14 | 15 | 1. Redistributions of source code must retain the above copyright notice, this 16 | list of conditions and the following disclaimer. 17 | 18 | 2. Redistributions in binary form must reproduce the above copyright notice, 19 | this list of conditions and the following disclaimer in the documentation 20 | and/or other materials provided with the distribution. 21 | 22 | 3. Neither the name of the copyright holder nor the names of its 23 | contributors may be used to endorse or promote products derived from 24 | this software without specific prior written permission. 25 | 26 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 27 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 28 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 29 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 30 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 31 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 32 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 33 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 34 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 35 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Creating a Stata project with automated Docker builds 2 | 3 | [![Build docker image](https://github.com/AEADataEditor/stata-project-with-docker/actions/workflows/build.yml/badge.svg)](https://github.com/AEADataEditor/stata-project-with-docker/actions/workflows/build.yml)[![Compute analysis](https://github.com/AEADataEditor/stata-project-with-docker/actions/workflows/compute.yml/badge.svg)](https://github.com/AEADataEditor/stata-project-with-docker/actions/workflows/compute.yml) 4 | 5 | ## Purpose 6 | 7 | This repository serves as a demonstration and a template on how to use Docker together with Stata to 8 | 9 | a) encapsulate a project's computing for reliability and reproducibility and 10 | b) (optionally) leverage cloud resources to test that functionality every time a piece of code changes. 11 | 12 | These short instructions should get you up and running fairly quickly. 13 | 14 | ## Requirements 15 | 16 | You will need 17 | 18 | - [ ] A Stata license file `stata.lic`. You will find this in your local Stata install directory. 19 | 20 | To run this locally on your computer, you will need 21 | 22 | - [ ] [Docker](https://docs.docker.com/get-docker/) or [Singularity](https://github.com/sylabs/singularity/releases). 23 | 24 | To run this in the cloud, you will need 25 | 26 | - [ ] A Github account, if you want to use the cloud functionality explained here as-is. Other methods do exist. 27 | - [ ] A Docker Hub account, to store the image. Other "image registries" exist and can be used, but are not covered in these instructions. 28 | 29 | 30 | ## Steps 31 | 32 | ### Creating an image locally 33 | 34 | 1. [ ] You should copy this template to your own personal space. You can do this in several ways: 35 | - Best way: Use the "[Use this template](https://github.com/AEADataEditor/stata-project-with-docker/generate)" button on the [main Github page for this project](https://github.com/AEADataEditor/stata-project-with-docker/). Then clone your version of this repository to your local machine and navigate to the folder where it was saved. 36 | - Good: [Fork the Github repository](https://github.com/AEADataEditor/stata-project-with-docker) by clicking on **Fork** in the top-right corner. Then clone your version of this repository to your local machine and navigate to the folder where it was saved. 37 | - OK: [Download](https://github.com/AEADataEditor/stata-project-with-docker/archive/refs/heads/main.zip) this project, expand on your computer and navigate to the folder where it was saved. 38 | 2. [ ] [Adjust the `Dockerfile`](#adjust-the-dockerfile). 39 | 3. [ ] [Adjust the `setup.do` file](#use-custom-setup-do-file) 40 | 4. [ ] [Build the Docker image](#build-the-image) 41 | 5. [ ] [Run the Docker image](#run-the-image) 42 | 43 | ### Leveraging cloud functionality 44 | 45 | 6. [ ] Upload the image to Docker Hub 46 | 7. [ ] Sync your code with your Github repository (which you created in Step 1, by using the template or forking) 47 | 8. [ ] Configure your Stata license in the cloud (securely) 48 | 9. [ ] Verify that the code runs in the cloud 49 | 50 | If you want to go the extra step 51 | 52 | 10. [ ] Setup building the Docker image in the cloud 53 | 54 | ## Details 55 | 56 | ### Adjust the Dockerfile 57 | 58 | The [Dockerfile](Dockerfile) contains instructions to build the container. You can edit it locally by opening it on your clone of this repository to make adjustments that match your own needs and preferences. 59 | 60 | #### Set Stata version 61 | 62 | To specify the Stata version of your choice, go to [https://hub.docker.com/u/dataeditors](https://hub.docker.com/u/dataeditors), click on the version you want to use (the one you have a license for), then go to "tags" and see what is the latest available tag for this version. Then, edit the global `SRCVERSION` to match the desired version and the global `SRCTAG` to match the name of the latest tag. 63 | 64 | ``` 65 | ARG SRCVERSION=17 66 | ARG SRCTAG=2021-10-13 67 | ARG SRCHUBID=dataeditors 68 | ``` 69 | 70 | #### Use custom setup do-file 71 | 72 | If you already have a setup file that installs all of your Stata packages, you do not need to rename it, simply change the following line: 73 | 74 | ``` 75 | COPY setup.do /setup.do 76 | ``` 77 | 78 | to read 79 | 80 | ``` 81 | COPY your_fancy_name.do /setup.do 82 | ``` 83 | 84 | If your file name has spaces (not a good idea), you may need to quote the first part (YMMV). 85 | 86 | ### Adjust the setup.do file 87 | 88 | The template repository contains a `setup.do` as an example. It should include all commands that are required to be run for "setting up" the project on a brand new system. In particular, it should install all needed Stata packages. For additional sample commands, see [https://github.com/gslab-econ/template/blob/master/config/config_stata.do](https://github.com/gslab-econ/template/blob/master/config/config_stata.do). 89 | 90 | ``` 91 | local ssc_packages "estout" 92 | 93 | // local ssc_packages "estout boottest" 94 | 95 | if !missing("`ssc_packages'") { 96 | foreach pkg in `ssc_packages' { 97 | dis "Installing `pkg'" 98 | ssc install `pkg', replace 99 | } 100 | } 101 | ``` 102 | 103 | ### Build the image 104 | 105 | By default, the build process is documented in [`build.sh`](build.sh) and works on Linux and macOS, but all commands can be run individually as well. Running this script will create a docker image with instructions to build your container. 106 | 107 | #### Set initial configurations 108 | 109 | You should edit the contents of the [`init.config.txt`](init.config.txt): 110 | 111 | ```{bash} 112 | VERSION=17 113 | # the TAG can be anything, but could be today's date 114 | TAG=$(date +%F) 115 | # This should be your login on Docker Hub 116 | MYHUBID=larsvilhuber 117 | # This can be anything, but might be the current repository name 118 | MYIMG=projectname 119 | # Identify where your Stata license may be. This can be hard-coded, or can be a function. 120 | # Remember to quote any strings that have spaces 121 | # STATALIC="/home/user/Stata 17/STATA.LIC" 122 | # STATALIC="/usr/local/stata17/stata.lic" 123 | STATALIC="$(find $HOME/Dropbox/ -name stata.lic.$VERSION| tail -1)" 124 | 125 | 126 | ``` 127 | 128 | You will want to adjust the variables. 129 | 130 | - `MYHUBID` is your login on Docker Hub 131 | - `MYIMG` is the name by which you will refer to this image. A very convenient `MYIMG` name might be the same as the Github repository name (replace `projectname` with `${PWD##*/}`), but it can be anything. 132 | - `TAG` You can version with today's date (which is what `date +%F` prints out), or anything else. 133 | - `STATALIC` contains the path to your Stata license. We need this to both build and later run the image. 134 | 135 | #### Run [`build.sh`](build.sh) 136 | 137 | Running the shell script [`build.sh`](build.sh) will leverage the existing Stata Docker image, add your project-specific details as specified in the [`Dockerfile`](Dockerfile), install any Stata packages as specified in the setup program, and store the project-specific Docker image locally on your computer. It will also write out the chosen configuration into `config.txt`. You will then be able to use that image to run your project's code **in the cloud or on the same machine as you built it**. 138 | 139 | 1. Open the terminal 140 | 2. Navigate to the folder where [`build.sh`](build.sh) is stored: 141 | 142 | ``` 143 | cd /your/file/path 144 | ``` 145 | 146 | 3. Run the shell script: 147 | 148 | ``` 149 | source build.sh 150 | ``` 151 | 152 | If you get an *Access denied* error message or one that says not possible to *connect to the Docker deamon*, see [section on error messages, below](#error-messages). 153 | 154 | ### Run the image 155 | 156 | The script [`run.sh`](run.sh) will pick up the configuration information in `config.txt`, and run your project inside the container image. If you have a terminal session open where you have already followed steps 1-3 in [Build the image](#build-the-image), you can simple run `source run.sh`. Otherwise, follow steps 1 and 2 above and then run `source run.sh`. 157 | 158 | - The image maps the project directory in the sample repository into the image as `/project/`. 159 | - The sample code [`code/main.do`](code/main.do) can be used as a template for your own main file. 160 | - Your output will appear wherever Stata code writes it to. For best practice, you should write to a `results` directory. 161 | 162 | ## Cloud functionality 163 | 164 | Once you have ascertained that everything is working fine, you can let the cloud run the Docker image in the future. Note that this assumes that all data can be either downloaded on the fly, or is available in the `data/` directory within Github (only recommended for quite small data). There are other ways of accessing large quantities of data (Git LFS, downloading from the internet, leveraging Dropbox, Box, or Google Drive), but those are beyond the scope for these instructions. 165 | 166 | To run code in the cloud, we will leverage a Github functionality called "[Github Actions](https://docs.github.com/en/actions/quickstart)". Similar systems elsewhere might be called "pipelines", "workflows", etc. The terminology below is focused on Github Actions, but generically, this can work on any one of those systems. 167 | 168 | ### Setting up Github Actions and Configure the Stata license in the cloud 169 | 170 | Your Stata license is valuable, and should not be posted to Github! However, we need it there in order to run the Docker image. Github and other cloud providers have the ability to store "secure" environment variables, that are made available to their systems. Github calls these "[secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets)" because, well, they are meant to remain secret. However, secrets are text, not files. So we need a workaround to store our Stata license as a file in the cloud. You will need a Bash shell for the following step. You can either do it from the command line, using the [`gh` command line tool](https://github.com/cli/cli), or generate the text, and copy and paste it in the web interface, as described [here](https://docs.github.com/en/actions/security-guides/encrypted-secrets). 171 | 172 | To run the image, the license needs to be available to the Github Action as `STATA_LIC_BASE64` in "base64" format. From a Linux/macOS command line, you can generate it like this: 173 | 174 | ```bash 175 | gh secret set STATA_LIC_BASE64 -b"$(cat stata.lic | base64)" -v all -o YOURORG 176 | ``` 177 | 178 | where `stata.lic` is your Stata license file, and `YOURORG` is your organization (can be dropped if running in your personal account). 179 | 180 | 181 | ### Publish the image 182 | 183 | In order to run this in the cloud, the "cloud" needs to be able to access the image you just created. You thus need to upload it to [Docker Hub](https://hub.docker.com/). You may need to login to do this. 184 | 185 | 186 | ``` 187 | source config.txt 188 | docker push $MYHUBID/${MYIMG}:$TAG 189 | ``` 190 | 191 | ### Sync your Git repository 192 | 193 | We assume you created a Git repository. If not, do it now! Assuming you have committed all files (in particular, `config.txt`, `run.sh`, and all your Stata code), you should push it to your Github repository: 194 | 195 | ``` 196 | git push 197 | ``` 198 | 199 | Note that this also enables you to use that same image on other computers you have access to, without rebuilding it: Simply `clone` your Github repository, and run `run.sh`. This will download the image we uploaded in the previous step, and run your code. This might be useful if you are running on a university cluster, or your mother-in-law's laptop during Thanksgiving. However, here we concentrate on the cloud functionality. 200 | 201 | ### Getting it to work in the cloud 202 | 203 | By default, this template repository has a pre-configured Github Actions workflow, stored in [`.github/workflows/compute.yml`](.github/workflows/compute.yml). There are, again, a few key parameters that can be configured. The first is the `on` parameter, which configures when actions are triggered. In the case of the template file, 204 | 205 | ``` 206 | on: 207 | push: 208 | branches: 209 | - 'main' 210 | workflow_dispatch: 211 | ``` 212 | 213 | which instructs the Github Action (run Stata on the code) to be triggered either by a commit to the `main` branch, or to be manually triggered, by going to the "Actions" tab in the Github Repository. The latter is very helpful for debugging! 214 | 215 | ### Results 216 | 217 | If you only run the code for testing purposes, you may simply be interested in whether or not the tests run successfully, and not in the outputs per se. However, if you wish to use this to actually run your code and retain meaningful results, then the last part of the [`.github/workflows/compute.yml`](.github/workflows/compute.yml) is relevant: 218 | 219 | ``` 220 | - name: Deploy 221 | uses: peaceiris/actions-gh-pages@v3.8.0 222 | with: 223 | github_token: ${{ secrets.GITHUB_TOKEN }} 224 | publish_dir: . 225 | publish_branch: results 226 | keep_files: true 227 | ``` 228 | In this case, once the code has run, the entire repository is pushed back to the "[results](https://github.com/AEADataEditor/stata-project-with-docker/tree/results)" branch. Alternatives consist in building and displaying a web page with results (in which case you might want to use the standard `gh-pages` branch), or actually compiling a LaTeX paper with all the results. 229 | 230 | If you are not interested in the outcomes, then simply deleting those lines is sufficient. 231 | 232 | If you want to be really fancy (we are), then you show a badge showing the latest result of the `compute` run (which in our case, demonstrates that this project is reproducible!): [![Compute analysis](https://github.com/AEADataEditor/stata-project-with-docker/actions/workflows/compute.yml/badge.svg)](https://github.com/AEADataEditor/stata-project-with-docker/actions/workflows/compute.yml). 233 | 234 | ## Going the extra step 235 | 236 | If we can run the Docker image in the cloud, can we also create the Docker image in the cloud? The answer, of course, is yes. 237 | 238 | 239 | ### Configurating Docker builds in the cloud 240 | 241 | This is pre-configured in [`.github/workflows/build.yml`](.github/workflows/build.yml). Reviewing this file shows a slightly different trigger: 242 | 243 | ``` 244 | on: 245 | push: 246 | branches: 247 | - 'main' 248 | paths: 249 | - 'Dockerfile' 250 | workflow_dispatch: 251 | ``` 252 | 253 | Here, only changes to the `Dockerfile` trigger a rebuild. While that may seem reasonable, we might also want to include `setup.do`, or other files that affect the Docker image. However, we can also manually trigger the rebuild in the "Actions" tab. 254 | 255 | ### Additional secrets 256 | 257 | We will need two additional "secrets", in order to be able to push to the Docker Hub from the cloud. 258 | 259 | ``` 260 | DOCKERHUB_USERNAME 261 | DOCKERHUB_TOKEN 262 | ``` 263 | 264 | See [the Docker Hub documentation](https://docs.docker.com/docker-hub/access-tokens/) on how to generate the latter. 265 | 266 | ### Running it 267 | 268 | The [`.github/workflows/build.yml`](.github/workflows/build.yml) workflow will run through all the necessary steps to publish an image. Note that there's a slight difference in what it does: it will always create a "latest" tag, not a date- or release-specific tag. However, you can always associate a specific tag with the latest version manually. And because we are really fancy, we also have a badge for that: 269 | [![Build docker image](https://github.com/AEADataEditor/stata-project-with-docker/actions/workflows/build.yml/badge.svg)](https://github.com/AEADataEditor/stata-project-with-docker/actions/workflows/build.yml). 270 | 271 | ### Not running it 272 | 273 | If you do not wish to build in the cloud, simply deleting [`.github/workflows/build.yml`](.github/workflows/build.yml) will disable that functionality. 274 | 275 | ## Other options 276 | 277 | We have described how to do this in a fairly general way. However, other methods to accomplish the same goal exist. Interested parties should check out the [ledwindra](https://github.com/ledwindra/continuous-integration-stata) and [labordynamicsinstitute](https://github.com/labordynamicsinstitute/continuous-integration-stata) versions of a pre-configured "Github Action" that does not require the license file, but instead requires the license information (several more secrets to configure). If "continuous integration" is not a concern but a cloud-based Stata+Docker setup is of interest, both [CodeOcean](https://codeocean.com) and (soon) [WholeTale](https://wholetale.org) offer such functionality. 278 | 279 | ## Conclusion 280 | 281 | The ability to conduct "continuous integration" in the cloud with Stata is a powerful tool to ensure that the project is reproducible at any time, and to learn early on when reproducibility is broken. For small projects, this template repository and tutorial is sufficient to get you started. For more complex projects, running it locally based on this template will also ensure reproducibility. 282 | 283 | ## Comments 284 | 285 | For any comments or suggestions, please [create an issue](https://github.com/AEADataEditor/stata-project-with-docker/issues/new/choose) or contact us on [Twitter as @AeaData](https://twitter.com/AeaData). 286 | 287 | --- 288 | 289 | ## Error messages 290 | 291 | 292 | - If you get an *Access denied* or *connect to the Docker daemon* error, that means your system (typically Linux) is not configured to allow you to run `docker` commands as a regular user. There are three different solutions to this issue: 293 | 294 | 1. Best practice: run docker in [rootless mode](https://docs.docker.com/engine/security/rootless/) 295 | 1. Good: [add your user to the docker group](https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user) 296 | 1. Bad practice: run docker as root. If you really need to choose this solution, modify the scripts 297 | - by editing line 48 in [`run.sh`](run.sh) and adding `sudo` before `docker run` 298 | - by editing line 17 in [`build.sh`](build.sh) and adding `sudo` before `DOCKER_BUILDKIT=1 docker build` 299 | 300 | - If you get a *connection refused* error: 301 | 302 | ``` 303 | failed to solve with frontend dockerfile.v0: failed to solve with 304 | frontend gateway.v0: failed to do request: Head "https://registry-1. 305 | docker.io/v2/docker/dockerfile/manifests/1.2": dial tcp: lookup 306 | registry-1.docker.io on [::1]:53: read udp [::1]:53098->[::1]:53: read: 307 | connection refused 308 | ``` 309 | then you may need to run `docker login` first. -------------------------------------------------------------------------------- /assets/docker-hub-dataeditors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AEADataEditor/stata-project-with-docker/12686f43191fe34430d05d67bc50a7cb1bb7b282/assets/docker-hub-dataeditors.png -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # for debugging 4 | BUILDARGS="--progress plain --no-cache" 5 | 6 | 7 | # if we are on Github Actions 8 | if [[ $CI ]] 9 | then 10 | DOCKERIMG=$(echo $GITHUB_REPOSITORY | tr [A-Z] [a-z]) 11 | TAG=latest 12 | else 13 | source init.config.txt 14 | DOCKERIMG=$(echo $MYHUBID/$MYIMG | tr [A-Z] [a-z]) 15 | fi 16 | 17 | # Check that the configured STATALIC is actually a file 18 | if [[ ! -f $STATALIC ]] 19 | then 20 | echo "You specified $STATALIC - that is not a file" 21 | exit 2 22 | fi 23 | 24 | DOCKER_BUILDKIT=1 docker build \ 25 | $BUILDARGS \ 26 | . \ 27 | --secret id=statalic,src=$STATALIC \ 28 | -t ${DOCKERIMG}:$TAG 29 | 30 | if [[ $? == 0 ]] 31 | then 32 | # write out final values to config 33 | [[ -f config.txt ]] && \rm -i config.txt 34 | echo "# configuration created on $(date +%F_%H:%M)" | tee config.txt 35 | for name in $(grep -Ev '^#' init.config.txt| awk -F= ' { print $1 } ') 36 | do 37 | echo ${name}=${!name} >> config.txt 38 | done 39 | fi 40 | 41 | -------------------------------------------------------------------------------- /code/main.do: -------------------------------------------------------------------------------- 1 | /* main.do */ 2 | /* Author: Lars Vilhuber */ 3 | /* NOTE: this is a VERY simple example file 4 | it does NOT fully comply with best practices 5 | */ 6 | 7 | // in case we need it 8 | local tmp : pwd 9 | // find out where the main.do is 10 | capture confirm file "code/main.do" 11 | if _rc != 0 { 12 | display as text "We may be in the code directory" 13 | capture confirm file "main.do" 14 | if _rc != 0 { 15 | display as text "not sure how to run" 16 | exit 17 | } 18 | // if yes, we go up 19 | cd ".." 20 | } 21 | 22 | global BASEDIR : pwd 23 | 24 | global DATADIR "${BASEDIR}/data" 25 | global CODEDIR "${BASEDIR}/code" 26 | global RESULTS "${BASEDIR}/results" 27 | 28 | sysuse auto 29 | desc 30 | 31 | /* we list the ado files - by default, it should list 'estout' 32 | that we installed via the setup.do during the build phase 33 | of the Docker image */ 34 | 35 | ado 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /config.txt: -------------------------------------------------------------------------------- 1 | # configuration created on 2022-02-16_21:36 2 | VERSION=17 3 | TAG=2022-02-16 4 | MYHUBID=larsvilhuber 5 | MYIMG=projectname 6 | STATALIC=/home/user/Dropbox/licenses/stata.lic.17 7 | -------------------------------------------------------------------------------- /data/README.md: -------------------------------------------------------------------------------- 1 | # Data directory 2 | 3 | Any data should go here. 4 | 5 | -------------------------------------------------------------------------------- /init.config.txt: -------------------------------------------------------------------------------- 1 | VERSION=17 2 | # the TAG can be anything, but could be today's date 3 | TAG=$(date +%F) 4 | # This should be your login on Docker Hub 5 | MYHUBID=larsvilhuber 6 | # This can be anything, but might be the current repository name 7 | MYIMG=projectname 8 | # Identify where your Stata license may be. This can be hard-coded, or can be a function. 9 | # Remember to quote any strings that have spaces 10 | # STATALIC="/home/user/Stata 17/STATA.LIC" 11 | # STATALIC="/usr/local/stata17/stata.lic" 12 | STATALIC="$(find $HOME/Dropbox/ -name stata.lic.$VERSION| tail -1)" 13 | 14 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ -f config.txt ]] 4 | then 5 | configfile=config.txt 6 | else 7 | configfile=init.config.txt 8 | fi 9 | 10 | # name of the file to run 11 | file=code/main.do 12 | 13 | 14 | echo "================================" 15 | echo "Pulling defaults from ${configfile}:" 16 | cat $configfile 17 | echo "--------------------------------" 18 | 19 | source $configfile 20 | 21 | echo "================================" 22 | echo "Running docker:" 23 | set -ev 24 | 25 | # When we are on Github Actions 26 | if [[ $CI ]] 27 | then 28 | DOCKEROPTS="--rm" 29 | DOCKERIMG=$(echo $GITHUB_REPOSITORY | tr [A-Z] [a-z]) 30 | TAG=latest 31 | else 32 | DOCKEROPTS="-it --rm -u $(id -u ${USER}):$(id -g ${USER}) " 33 | DOCKERIMG=$(echo $MYHUBID/$MYIMG | tr [A-Z] [a-z]) 34 | fi 35 | 36 | # ensure that the directories are writable by Docker 37 | chmod a+rwX code code/* 38 | chmod a+rwX data 39 | 40 | # a few names 41 | basefile=$(basename $file) 42 | codedir=$(dirname $file) 43 | logfile=${file%*.do}.log 44 | 45 | # run the docker and the Stata file 46 | # note that the working directory will be set to '/code' by default 47 | 48 | time docker run $DOCKEROPTS \ 49 | -v ${STATALIC}:/usr/local/stata/stata.lic \ 50 | -v $(pwd)/:/project \ 51 | -w /project/code \ 52 | $DOCKERIMG:$TAG -b $basefile 53 | 54 | # print and check logfile 55 | 56 | EXIT_CODE=0 57 | if [[ -f $logfile ]] 58 | then 59 | echo "===== $logfile =====" 60 | cat $logfile 61 | 62 | # Fail CI if Stata ran with an error 63 | LOG_CODE=$(tail -1 $logfile | tr -d '[:cntrl:]') 64 | echo "===== LOG CODE: $LOG_CODE =====" 65 | [[ ${LOG_CODE:0:1} == "r" ]] && EXIT_CODE=1 66 | else 67 | echo "$logfile not found" 68 | EXIT_CODE=2 69 | fi 70 | echo "==== Exiting with code $EXIT_CODE" 71 | exit $EXIT_CODE 72 | 73 | 74 | -------------------------------------------------------------------------------- /setup.do: -------------------------------------------------------------------------------- 1 | /* setup.do */ 2 | sysdir 3 | 4 | /* add packages to the macro */ 5 | 6 | * *** Add required packages from SSC to this list *** 7 | local ssc_packages "estout" 8 | 9 | // local ssc_packages "estout boottest" 10 | 11 | if !missing("`ssc_packages'") { 12 | foreach pkg in `ssc_packages' { 13 | dis "Installing `pkg'" 14 | ssc install `pkg', replace 15 | } 16 | } 17 | 18 | * Install packages using net 19 | * net install yaml, from("https://raw.githubusercontent.com/gslab-econ/stata-misc/master/") 20 | 21 | /* other commands */ 22 | 23 | /* after installing all packages, it may be necessary to issue the mata mlib index command */ 24 | mata: mata mlib index 25 | 26 | 27 | set more off, perm 28 | 29 | 30 | --------------------------------------------------------------------------------