├── .gitignore ├── 1startGitLab.sh ├── 2createProjectsAndCommitToGitLab.sh ├── 3startDockerRegistry.sh ├── 4startJenkins.sh ├── README.md ├── hello-world-app-acceptance ├── .gitignore ├── pom.xml └── src │ └── test │ └── java │ └── de │ └── philipphauer │ └── helloworld │ └── DummyAcceptanceTest.java ├── hello-world-app-deployment └── runDockerContainer.sh ├── hello-world-app ├── .gitignore ├── HelloWorldApplication.launch ├── README ├── pom.xml └── src │ ├── main │ ├── docker │ │ └── docker-assembly.xml │ ├── java │ │ └── de │ │ │ └── philipphauer │ │ │ └── helloworld │ │ │ ├── HelloWorldApplication.java │ │ │ ├── HelloWorldConfiguration.java │ │ │ ├── healthchecks │ │ │ └── TemplateHealthCheck.java │ │ │ ├── model │ │ │ └── Saying.java │ │ │ └── rest │ │ │ └── HelloWorldResource.java │ └── resources │ │ └── config.yml │ └── test │ ├── java │ └── de │ │ └── philipphauer │ │ └── helloworld │ │ └── rest │ │ └── HelloWorldResourceTest.java │ └── resources │ └── test-config.yml └── util-scripts └── docker-util.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .project -------------------------------------------------------------------------------- /1startGitLab.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # https://hub.docker.com/r/sameersbn/gitlab/ 3 | # https://github.com/sameersbn/docker-gitlab 4 | 5 | #TODO use docker-compose instead (see doc) 6 | 7 | #postgresql 8 | docker run --name gitlab-postgresql -d \ 9 | --env 'DB_NAME=gitlabhq_production' \ 10 | --env 'DB_USER=gitlab' --env 'DB_PASS=password' \ 11 | --volume /srv/docker/gitlab/postgresql:/var/lib/postgresql \ 12 | sameersbn/postgresql:9.4-3 13 | 14 | #redis 15 | docker run --name gitlab-redis -d \ 16 | --volume /srv/docker/gitlab/redis:/var/lib/redis \ 17 | sameersbn/redis:latest 18 | 19 | #gitlab 20 | docker run --name gitlab -d \ 21 | --link gitlab-postgresql:postgresql --link gitlab-redis:redisio \ 22 | --publish 10022:22 --publish 10080:80 \ 23 | --env 'GITLAB_PORT=10080' --env 'GITLAB_SSH_PORT=10022' \ 24 | --volume /srv/docker/gitlab/gitlab:/home/git/data \ 25 | sameersbn/gitlab:7.14.3 26 | 27 | echo "NOTE: Please allow a couple of minutes for the GitLab application to start." 28 | echo "Port: 10080. user: root. default pw: 5iveL!fe or changed to 12345678" 29 | -------------------------------------------------------------------------------- /2createProjectsAndCommitToGitLab.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # use same credentials for git as for gitlab user 4 | 5 | echo "precondition: create project 'hello-world-app' in gitlab" 6 | echo "The default user is 'root' and the pw is shown above" 7 | 8 | TARGETFOLDER=~/Development 9 | GITLAB_USER=root 10 | GITLAB_PW=12345678 11 | 12 | createProjectAndCommitToGitLab(){ 13 | local projectName=$1 14 | local targetPath=$TARGETFOLDER/$projectName 15 | echo "=== $targetPath ===" 16 | 17 | rm -fr $targetPath 18 | 19 | echo "--- Cloning..." 20 | git clone http://$GITLAB_USER:$GITLAB_PW@localhost:10080/root/$projectName.git $targetPath 21 | git config user.name $GITLAB_USER #don't use --global! 22 | git config credential.helper cache #caches password for 15 min 23 | 24 | echo "--- Copying..." 25 | cp -r $projectName $TARGETFOLDER 26 | rm -fr $targetPath/target 27 | rm -fr $targetPath/.settings 28 | rm -fr $targetPath/.classpath 29 | rm -fr $targetPath/.project 30 | 31 | echo "--- Committing..." 32 | local currentPath=`pwd`; 33 | cd $targetPath 34 | git add . 35 | git commit -m "inital commit" 36 | git push -u origin master 37 | cd $currentPath 38 | } 39 | 40 | createProjectAndCommitToGitLab hello-world-app 41 | createProjectAndCommitToGitLab hello-world-app-acceptance 42 | createProjectAndCommitToGitLab hello-world-app-deployment 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /3startDockerRegistry.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | PORT=5000 3 | VOLUME_PATH=~/docker-registry-data 4 | docker run -d -p $PORT:5000 --restart=always --name registry \ 5 | -v $VOLUME_PATH:/var/lib/registry \ 6 | registry:2 7 | 8 | echo "Docker Registry runs on port $PORT" 9 | echo "Prefix tag with localhost:5000 so that it points to the registry" 10 | echo "Access http://localhost:5000/v2/_catalog in the browser to show the content. Or http://localhost:5000/v2/prozu-service/tags/list" 11 | echo "See https://docs.docker.com/registry/spec/api/#detail for documentation" 12 | echo "Also you can see the docker content in $VOLUME_PATH" 13 | -------------------------------------------------------------------------------- /4startJenkins.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | JENKINS_PORT=8090 3 | JENKINS_CONTAINER_NAME=jenkins 4 | JENKINS_HOME=~/jenkins_home 5 | 6 | mkdir $JENKINS_HOME 7 | 8 | JenkinsContainerId=`docker ps -qa --filter "name=$JENKINS_CONTAINER_NAME"` 9 | if [ -n "$JenkinsContainerId" ] 10 | then 11 | echo "Stopping and removing existing jenkins container" 12 | docker stop $JENKINS_CONTAINER_NAME 13 | docker rm $JENKINS_CONTAINER_NAME 14 | fi 15 | 16 | echo "Starting jenkins container on port $JENKINS_PORT and jenkins home is $JENKINS_HOME" 17 | # https://github.com/jenkinsci/docker 18 | # https://hub.docker.com/r/jenkinsci/jenkins/tags/ 19 | # /var/jenkins_home contains all plugins and configuration 20 | docker run -d --name $JENKINS_CONTAINER_NAME \ 21 | -p $JENKINS_PORT:8080 -p 50000:50000 \ 22 | -v $JENKINS_HOME:/var/jenkins_home \ 23 | -v /var/run/docker.sock:/var/run/docker.sock \ 24 | -v $(which docker):/bin/docker \ 25 | -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/lib/x86_64-linux-gnu/libapparmor.so.1 \ 26 | -u root \ 27 | jenkins:1.609.3 28 | 29 | #the last 3 volume bindings are important in order to enable jenkins to run docker, see 30 | #http://jpetazzo.github.io/2015/09/03/do-not-use-docker-in-docker-for-ci/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Continuous Delivery Playground 2 | My playground for setting up a continuous delivery pipeline with Docker. 3 | 4 | - Jenkins 5 | - GitLab 6 | - Using a simple Dropwizard microservice as an example application 7 | - Using a Docker image (containing the microservice) to create reproducible environments for all stages of the continuous delivery pipeline. 8 | 9 | # Step by Step Tutorial 10 | For a step by step tutorial see my blog post ["Tutorial: Continuous Delivery with Docker and Jenkins"](http://blog.philipphauer.de/tutorial-continuous-delivery-with-docker-jenkins/#Setting_up_a_Simple_Continuous_Delivery_Pipeline_with_Docker). 11 | -------------------------------------------------------------------------------- /hello-world-app-acceptance/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .settings 3 | .classpath 4 | .project 5 | /target/ 6 | -------------------------------------------------------------------------------- /hello-world-app-acceptance/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | de.philipphauer 5 | hello-world-app-acceptance 6 | 0.0.1-SNAPSHOT 7 | 8 | UTF-8 9 | UTF-8 10 | hello-world-app 11 | 8080 12 | localhost 13 | ${docker.host.address}:5000/ 14 | ${docker.registry.name}${target.project} 15 | 16 | 17 | 18 | junit 19 | junit 20 | 4.12 21 | 22 | 23 | 24 | 25 | 26 | org.apache.maven.plugins 27 | maven-surefire-plugin 28 | 2.18.1 29 | 30 | true 31 | 32 | 33 | 34 | org.apache.maven.plugins 35 | maven-failsafe-plugin 36 | 2.18.1 37 | 38 | integration-test 39 | 40 | **/*.java 41 | 42 | 43 | http://${docker.host.address}:${prozu.port}/ 44 | 45 | 46 | 47 | 48 | 49 | integration-test 50 | verify 51 | 52 | 53 | 54 | 55 | 56 | org.jolokia 57 | docker-maven-plugin 58 | 0.13.6 59 | 60 | 61 | 62 | ${target.project} 63 | ${docker.repository.name}:${project.version} 64 | 65 | alias 66 | 67 | ${prozu.port}:8080 68 | ${prozu.port.admin}:8081 69 | 70 | 71 | 72 | ${user.home}/logs:/logs 73 | 74 | 75 | 76 | org.eclipse.jetty.server.Server: Started @ 77 | 78 | 79 | 80 | ${project.artifactId} 81 | cyan 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | start 90 | pre-integration-test 91 | 92 | start 93 | 94 | 95 | 96 | stop 97 | post-integration-test 98 | 99 | stop 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /hello-world-app-acceptance/src/test/java/de/philipphauer/helloworld/DummyAcceptanceTest.java: -------------------------------------------------------------------------------- 1 | package de.philipphauer.helloworld; 2 | 3 | import java.io.IOException; 4 | import java.net.HttpURLConnection; 5 | import java.net.URL; 6 | 7 | import org.junit.Assert; 8 | import org.junit.Test; 9 | 10 | public class DummyAcceptanceTest { 11 | 12 | //TODO do real acceptance testing (GUI test with selenium or behavior test with jbehave) 13 | 14 | @Test 15 | public void testConnection() throws IOException { 16 | String urlString = System.getProperty("service.url"); 17 | System.out.println("testing url:" + urlString); 18 | 19 | URL serviceUrl = new URL(urlString + "hello-world"); 20 | HttpURLConnection connection = (HttpURLConnection) serviceUrl.openConnection(); 21 | int responseCode = connection.getResponseCode(); 22 | Assert.assertEquals(200, responseCode); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /hello-world-app-deployment/runDockerContainer.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | DOCKER_REPO=192.168.35.217:5000/hello-world-app 4 | TAG=0.0.1-SNAPSHOT 5 | CONTAINER_NAME=hello-world-app-deployment 6 | 7 | containerId=`docker ps -qa --filter "name=$CONTAINER_NAME"` 8 | if [ -n "$containerId" ] 9 | then 10 | echo "Stopping and removing existing hello-world container" 11 | docker stop $CONTAINER_NAME 12 | docker rm $CONTAINER_NAME 13 | fi 14 | 15 | docker run -d --name $CONTAINER_NAME -p 9090:8080 $DOCKER_REPO:$TAG 16 | #use -d to run in background 17 | #use -it to run in foreground and get the output. add --rm to remove the container (container's file system) when it exits. 18 | 19 | -------------------------------------------------------------------------------- /hello-world-app/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .settings 3 | .classpath 4 | .project 5 | /target/ 6 | -------------------------------------------------------------------------------- /hello-world-app/HelloWorldApplication.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /hello-world-app/README: -------------------------------------------------------------------------------- 1 | dummy project for continuous delivery 2 | -------------------------------------------------------------------------------- /hello-world-app/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | de.philipphauer 5 | hello-world-app 6 | 0.0.1-SNAPSHOT 7 | 8 | 0.8.0 9 | 8080 10 | 8081 11 | localhost:5000/ 12 | ${docker.registry.name}${project.artifactId} 13 | 14 | 15 | 16 | io.dropwizard 17 | dropwizard-core 18 | ${dropwizard.version} 19 | 20 | 21 | io.dropwizard 22 | dropwizard-testing 23 | ${dropwizard.version} 24 | 25 | 26 | junit 27 | junit 28 | 4.12 29 | 30 | 31 | io.dropwizard 32 | dropwizard-client 33 | ${dropwizard.version} 34 | 35 | 36 | 37 | 38 | 39 | org.apache.maven.plugins 40 | maven-shade-plugin 41 | 1.6 42 | 43 | true 44 | 45 | 46 | *:* 47 | 48 | META-INF/*.SF 49 | META-INF/*.DSA 50 | META-INF/*.RSA 51 | 52 | 53 | 54 | 55 | 56 | 57 | package 58 | 59 | shade 60 | 61 | 62 | 63 | 65 | 67 | de.philipphauer.helloworld.HelloWorldApplication 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | org.apache.maven.plugins 76 | maven-jar-plugin 77 | 2.4 78 | 79 | 80 | 81 | true 82 | 83 | 84 | 85 | 86 | 87 | org.jolokia 88 | docker-maven-plugin 89 | 0.13.3 90 | 91 | 92 | 93 | ${project.artifactId} 94 | ${docker.repository.name}:${project.version} 95 | 96 | java:8-jre 97 | phauer 98 | 99 | docker-assembly.xml 100 | 101 | 102 | 8080 103 | 8081 104 | 105 | 106 | java -jar \ 107 | /maven/${project.build.finalName}.jar server \ 108 | /maven/config.yml 109 | 110 | 111 | 112 | alias 113 | 114 | ${prozu.port}:8080 115 | ${prozu.port.admin}:8081 116 | 117 | 118 | 119 | ${user.home}/logs:/logs 120 | 121 | 122 | 123 | org.eclipse.jetty.server.Server: Started @ 124 | 125 | 126 | 127 | ${project.artifactId} 128 | cyan 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | start 137 | pre-integration-test 138 | 139 | build 140 | start 141 | 142 | 143 | 144 | stop 145 | post-integration-test 146 | 147 | stop 148 | 149 | 150 | 151 | push-to-docker-registry 152 | deploy 153 | 154 | push 155 | 156 | 157 | 158 | 159 | 160 | org.apache.maven.plugins 161 | maven-deploy-plugin 162 | 2.7 163 | 164 | true 165 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /hello-world-app/src/main/docker/docker-assembly.xml: -------------------------------------------------------------------------------- 1 | 3 | ${project.artifactId} 4 | 5 | 6 | target/${project.build.finalName}.jar 7 | / 8 | 9 | 10 | src/main/resources/config.yml 11 | / 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /hello-world-app/src/main/java/de/philipphauer/helloworld/HelloWorldApplication.java: -------------------------------------------------------------------------------- 1 | package de.philipphauer.helloworld; 2 | 3 | import de.philipphauer.helloworld.healthchecks.TemplateHealthCheck; 4 | import de.philipphauer.helloworld.rest.HelloWorldResource; 5 | import io.dropwizard.Application; 6 | import io.dropwizard.setup.Bootstrap; 7 | import io.dropwizard.setup.Environment; 8 | 9 | public class HelloWorldApplication extends Application { 10 | 11 | public static void main(String[] args) throws Exception { 12 | new HelloWorldApplication().run(args); 13 | } 14 | 15 | @Override 16 | public String getName() { 17 | return "hello-world"; 18 | } 19 | 20 | @Override 21 | public void initialize(Bootstrap bootstrap) { 22 | // nothing to do yet 23 | } 24 | 25 | @Override 26 | public void run(HelloWorldConfiguration configuration, Environment environment) { 27 | TemplateHealthCheck healthCheck = new TemplateHealthCheck(configuration.getTemplate()); 28 | environment.healthChecks().register("template", healthCheck); 29 | 30 | HelloWorldResource resource = new HelloWorldResource(configuration.getTemplate(), 31 | configuration.getDefaultName()); 32 | environment.jersey().register(resource); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /hello-world-app/src/main/java/de/philipphauer/helloworld/HelloWorldConfiguration.java: -------------------------------------------------------------------------------- 1 | package de.philipphauer.helloworld; 2 | 3 | import io.dropwizard.Configuration; 4 | 5 | import org.hibernate.validator.constraints.NotEmpty; 6 | 7 | import com.fasterxml.jackson.annotation.JsonProperty; 8 | 9 | /** mapping: YAML file to this class */ 10 | public class HelloWorldConfiguration extends Configuration { 11 | 12 | @NotEmpty 13 | private String template; 14 | 15 | @NotEmpty 16 | private String defaultName = "Stranger"; 17 | 18 | @JsonProperty 19 | public String getTemplate() { 20 | return template; 21 | } 22 | 23 | @JsonProperty 24 | public void setTemplate(String template) { 25 | this.template = template; 26 | } 27 | 28 | @JsonProperty 29 | public String getDefaultName() { 30 | return defaultName; 31 | } 32 | 33 | @JsonProperty 34 | public void setDefaultName(String name) { 35 | this.defaultName = name; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /hello-world-app/src/main/java/de/philipphauer/helloworld/healthchecks/TemplateHealthCheck.java: -------------------------------------------------------------------------------- 1 | package de.philipphauer.helloworld.healthchecks; 2 | 3 | import com.codahale.metrics.health.HealthCheck; 4 | 5 | public class TemplateHealthCheck extends HealthCheck { 6 | private final String template; 7 | 8 | public TemplateHealthCheck(String template) { 9 | this.template = template; 10 | } 11 | 12 | @Override 13 | protected Result check() throws Exception { 14 | final String saying = String.format(template, "TEST"); 15 | if (!saying.contains("TEST")) { 16 | return Result.unhealthy("template doesn't include a name"); 17 | } 18 | return Result.healthy(); 19 | } 20 | } -------------------------------------------------------------------------------- /hello-world-app/src/main/java/de/philipphauer/helloworld/model/Saying.java: -------------------------------------------------------------------------------- 1 | package de.philipphauer.helloworld.model; 2 | 3 | import org.hibernate.validator.constraints.Length; 4 | 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | 7 | public class Saying { 8 | private long id; 9 | 10 | @Length(max = 3) 11 | private String content; 12 | 13 | public Saying() { 14 | // Jackson deserialization 15 | } 16 | 17 | public Saying(long id, String content) { 18 | this.id = id; 19 | this.content = content; 20 | } 21 | 22 | @JsonProperty 23 | public long getId() { 24 | return id; 25 | } 26 | 27 | @JsonProperty 28 | public String getContent() { 29 | return content; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /hello-world-app/src/main/java/de/philipphauer/helloworld/rest/HelloWorldResource.java: -------------------------------------------------------------------------------- 1 | package de.philipphauer.helloworld.rest; 2 | 3 | import java.util.concurrent.atomic.AtomicLong; 4 | 5 | import javax.ws.rs.GET; 6 | import javax.ws.rs.Path; 7 | import javax.ws.rs.Produces; 8 | import javax.ws.rs.QueryParam; 9 | import javax.ws.rs.core.MediaType; 10 | 11 | import com.codahale.metrics.annotation.Timed; 12 | import com.google.common.base.Optional; 13 | 14 | import de.philipphauer.helloworld.model.Saying; 15 | 16 | @Path("/hello-world") 17 | @Produces(MediaType.APPLICATION_JSON) 18 | public class HelloWorldResource { 19 | private final String template; 20 | private final String defaultName; 21 | private final AtomicLong counter; 22 | 23 | public HelloWorldResource(String template, String defaultName) { 24 | this.template = template; 25 | this.defaultName = defaultName; 26 | this.counter = new AtomicLong(); 27 | } 28 | 29 | @GET 30 | @Timed 31 | public Saying sayHello(@QueryParam("name") Optional name) { 32 | final String value = String.format(template, name.or(defaultName)); 33 | return new Saying(counter.incrementAndGet(), value); 34 | } 35 | 36 | } 37 | // http://localhost:8081/metrics?pretty=true -------------------------------------------------------------------------------- /hello-world-app/src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | template: Hello, %s! 2 | defaultName: Stranger -------------------------------------------------------------------------------- /hello-world-app/src/test/java/de/philipphauer/helloworld/rest/HelloWorldResourceTest.java: -------------------------------------------------------------------------------- 1 | package de.philipphauer.helloworld.rest; 2 | 3 | import static org.hamcrest.CoreMatchers.equalTo; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | import io.dropwizard.client.JerseyClientBuilder; 6 | import io.dropwizard.testing.ResourceHelpers; 7 | import io.dropwizard.testing.junit.DropwizardAppRule; 8 | 9 | import javax.ws.rs.client.Client; 10 | import javax.ws.rs.core.Response; 11 | 12 | import org.junit.ClassRule; 13 | import org.junit.Test; 14 | 15 | import de.philipphauer.helloworld.HelloWorldApplication; 16 | import de.philipphauer.helloworld.HelloWorldConfiguration; 17 | 18 | public class HelloWorldResourceTest { 19 | 20 | @ClassRule 21 | public static final DropwizardAppRule RULE = 22 | new DropwizardAppRule(HelloWorldApplication.class, 23 | ResourceHelpers.resourceFilePath("test-config.yml")); 24 | 25 | @Test 26 | public void helloWorld() { 27 | Client client = new JerseyClientBuilder(RULE.getEnvironment()).build("test client"); 28 | String url = String.format("http://localhost:%d/hello-world", RULE.getLocalPort()); 29 | Response response = client.target(url).request().get(); 30 | String json = response.readEntity(String.class); 31 | assertThat(json, equalTo("{\"id\":1,\"content\":\"Hello, Stranger!\"}")); 32 | assertThat(response.getStatus(), equalTo(200)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /hello-world-app/src/test/resources/test-config.yml: -------------------------------------------------------------------------------- 1 | template: Hello, %s! 2 | defaultName: Stranger 3 | server: 4 | applicationConnectors: 5 | - type: http 6 | port: 9000 7 | adminConnectors: 8 | - type: http 9 | port: 9001 -------------------------------------------------------------------------------- /util-scripts/docker-util.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # add 'source /docker-util.sh' to ~/.bashrc. This makes this functions available in the current shell. 3 | docker-stop-remove(){ 4 | docker stop $1 && docker rm $1 5 | } 6 | docker-remove-stopped(){ 7 | #docker rm $(docker ps -a -q) 8 | docker ps -a -q | xargs docker rm 9 | } 10 | docker-nuke() { 11 | docker ps -q | xargs docker stop 12 | docker ps -q -a | xargs docker rm 13 | } 14 | docker-rmi-none() { 15 | docker images | grep '' | \ 16 | awk '{ print $3 }' | \ 17 | xargs docker rmi 18 | } 19 | docker-go() { 20 | docker run --rm -t -i $@ 21 | } 22 | docker-go-bg() { 23 | docker run -d --name $@ $@ 24 | } 25 | # append bash in a running container 26 | docker-exec-bash(){ 27 | if [ $# -lt 1 ] ; then 28 | echo "Please provide a container id or name. Usage: docker-exec-bash " 29 | else 30 | docker exec -it $1 bash 31 | fi 32 | } 33 | # show running processes within a running container 34 | docker-container-ps(){ 35 | docker exec $1 ps -f 36 | } 37 | 38 | 39 | --------------------------------------------------------------------------------