Nexus3 Infrastructure Seed Installation
As we are looking to automate our Nexus3 installation, we will be modifying the Dockerfile slightly to adjust initial startup behavior.
version: '3.8'
services:
nexus:
image: seed/sonatype/nexus3:1.0.0
build: .
FROM seed/java/openjdk-8:1.0.0
ARG CONTAINER_UID=2001
ARG CONTAINER_GID=2001
ARG CONTAINER_USER=nexus
ARG CONTAINER_GROUP=nexus
RUN groupadd -g ${CONTAINER_GID} ${CONTAINER_GROUP} \
&& useradd -d /home/${CONTAINER_USER} -u ${CONTAINER_UID} -g ${CONTAINER_GROUP} -m -s /bin/bash ${CONTAINER_USER}
RUN mkdir -p /opt/sonatype/nexus /opt/sonatype/sonatype-work/nexus3 \
&& ln -s /opt/sonatype/sonatype-work/nexus3 /nexus-data \
&& chown -R nexus:nexus /opt/sonatype /nexus-data
ARG NEXUS_VERSION=3.41.0-01
ARG NEXUS_CHECKSUM=ce265e627a665f9e833bf9c6e15a58c739882eb753863062f9e42e83e6f0d844
RUN curl -fsSL https://download.sonatype.com/nexus/3/nexus-${NEXUS_VERSION}-unix.tar.gz -o /tmp/nexus-${NEXUS_VERSION}-unix.tar.gz \
&& sha256sum /tmp/nexus-${NEXUS_VERSION}-unix.tar.gz \
&& echo "${NEXUS_CHECKSUM} /tmp/nexus-${NEXUS_VERSION}-unix.tar.gz" | sha256sum -c - \
&& tar -xvzf /tmp/nexus-${NEXUS_VERSION}-unix.tar.gz --strip-components=1 -C /opt/sonatype/nexus \
&& rm -rf /tmp/*
COPY nexus-default.properties /opt/sonatype/nexus/etc
USER nexus
CMD ["/opt/sonatype/nexus/bin/nexus","run"]
## DO NOT EDIT - CUSTOMIZATIONS BELONG IN $data-dir/etc/nexus.properties
##
# Jetty section
application-port=8081
application-host=0.0.0.0
nexus-args=${jetty.etc}/jetty.xml,${jetty.etc}/jetty-http.xml,${jetty.etc}/jetty-requestlog.xml
nexus-context-path=/
# Nexus section
nexus-edition=nexus-pro-edition
nexus-features=\
nexus-pro-feature
nexus.hazelcast.discovery.isEnabled=true
nexus.scripts.allowCreation=true
nexus.security.randompassword=false
The main change to our default Nexus3 Dockerfile is the addition of the nexus-default.properties that will slightly change how Nexus3 configures itself during the initial start. There are two specific configs in particular that we are most interest in to facilitate our headless install.
nexus.scripts.allowCreation=true
nexus.security.randompassword=false
Script creation allows us to execute new scripts for install while the randompassword ensures that a default known password will be used on first setup to execute automation scripts.
Simply build the final and adjusted nexus3 container image.
docker-compose -f seed/nexus/docker-compose.yml build
Automated Configuration
The first initialization step is using an initialization side car container that will connect to a running nexus3 container to apply configuration. With nexus3, configuration is done via a set of groovy scripts that provide a very extensible configuration mechanism.
/**
* An example script that handles adding or updating a groovy script via the REST API.
*/
@Grab('org.sonatype.nexus:nexus-rest-client:3.9.0-01')
@Grab('org.sonatype.nexus:nexus-rest-jackson2:3.9.0-01')
@Grab('org.sonatype.nexus:nexus-script:3.9.0-01')
@Grab('org.jboss.spec.javax.servlet:jboss-servlet-api_3.1_spec:1.0.0.Final')
@Grab('com.fasterxml.jackson.core:jackson-core:2.8.6')
@Grab('com.fasterxml.jackson.core:jackson-databind:2.8.6')
@Grab('com.fasterxml.jackson.core:jackson-annotations:2.8.6')
@Grab('com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:2.8.6')
@Grab('org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.0_spec:1.0.1.Beta1')
@Grab('org.jboss.spec.javax.annotation:jboss-annotations-api_1.2_spec:1.0.0.Final')
@Grab('javax.activation:activation:1.1.1')
@Grab('net.jcip:jcip-annotations:1.0')
@Grab('org.jboss.logging:jboss-logging-annotations:2.0.1.Final')
@Grab('org.jboss.logging:jboss-logging-processor:2.0.1.Final')
@Grab('com.sun.xml.bind:jaxb-impl:2.2.7')
@Grab('com.sun.mail:javax.mail:1.5.6')
@Grab('org.apache.james:apache-mime4j:0.6')
@GrabExclude('org.codehaus.groovy:groovy-all')
import javax.ws.rs.NotFoundException
import org.sonatype.nexus.script.ScriptClient
import org.sonatype.nexus.script.ScriptXO
import org.jboss.resteasy.client.jaxrs.BasicAuthentication
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder
CliBuilder cli = new CliBuilder(
usage: 'groovy addUpdateScript.groovy -u admin -p admin123 -f scriptFile.groovy [-n explicitName] [-h nx3Url]')
cli.with {
u longOpt: 'username', args: 1, required: true, 'A User with permission to use the NX3 Script resource'
p longOpt: 'password', args: 1, required: true, 'Password for given User'
f longOpt: 'file', args: 1, required: true, 'Script file to send to NX3'
h longOpt: 'host', args: 1, 'NX3 host url (including port if necessary). Defaults to http://localhost:8081'
n longOpt: 'name', args: 1, 'Name to store Script file under. Defaults to the name of the Script file.'
}
def options = cli.parse(args)
if (!options) {
return
}
def file = new File(options.f)
assert file.exists()
def host = options.h ?: 'http://localhost:8081'
def resource = 'service/rest'
ScriptClient scripts = new ResteasyClientBuilder()
.build()
.register(new BasicAuthentication(options.u, options.p))
.target("$host/$resource")
.proxy(ScriptClient)
String name = options.n ?: file.name
// Look to see if a script with this name already exists so we can update if necessary
boolean newScript = true
try {
scripts.read(name)
newScript = false
println "Existing Script named '$name' will be updated"
}
catch (NotFoundException e) {
println "Script named '$name' will be created"
}
def script = new ScriptXO(name, file.text, 'groovy')
if (newScript) {
scripts.add(script)
}
else {
scripts.edit(name, script)
}
println "Stored scripts are now: ${scripts.browse().collect { it.name }}"
import org.sonatype.nexus.blobstore.api.BlobStoreManager
import org.sonatype.nexus.repository.config.WritePolicy
def blobStoreProxy = blobStore.createFileBlobStore('apt-proxy', 'apt-proxy')
/**
* Create an Apt proxy repository.
* @param name The name of the new Repository
* @param remoteUrl The url of the external proxy for this Repository
* @param blobStoreName The BlobStore the Repository should use
* @param distribution The name of the required distribution
* @param strictContentTypeValidation Whether or not the Repository should enforce strict content types
* @return the newly created Repository
*/
/*
Repository createAptProxy(final String name,
final String remoteUrl,
final String blobStoreName,
final String distribution,
final boolean strictContentTypeValidation);
*/
repository.createAptProxy('apt-proxy-ubuntu-focal',
'http://archive.ubuntu.com/ubuntu/',
'apt-proxy',
'focal',
true)
log.info('Created Apt Ubuntu Focal')
repository.createAptProxy('apt-proxy-ubuntu-focal-microsoft',
'https://packages.microsoft.com/ubuntu/20.04/prod/',
'apt-proxy',
'focal',
true)
log.info('Created Apt Ubuntu Focal Microsoft')
repository.createAptProxy('apt-proxy-ubuntu-focal-microsoft-mssql',
'https://packages.microsoft.com/ubuntu/20.04/mssql-server-2019',
'apt-proxy',
'focal',
true)
log.info('Created Apt Ubuntu Focal Microsoft SQL')
repository.createAptProxy('apt-proxy-ubuntu-focal-docker',
'https://download.docker.com/linux/ubuntu/',
'apt-proxy',
'focal',
true)
log.info('Created Apt Ubuntu Focal Docker')
repository.createAptProxy('apt-proxy-ubuntu-focal-postgres',
'https://apt.postgresql.org/pub/repos/apt/',
'apt-proxy',
'focal-pgdg',
true)
log.info('Created Apt Ubuntu Focal Postgres')
log.info('Script aptRepositories completed successfully')
import org.sonatype.nexus.repository.config.WritePolicy
// create a new blob store dedicated to usage with raw repositories
def blobStore = blobStore.createFileBlobStore('raw-build-archive', 'raw-build-archive')
// for any files, only allow once
repository.createRawHosted('build-archive', blobStore.name, false, WritePolicy.ALLOW_ONCE)
log.info('Script buildArchiveRepositories completed successfully')
repository.getRepositoryManager().delete('maven-snapshots');
repository.getRepositoryManager().delete('maven-releases');
repository.getRepositoryManager().delete('maven-central');
repository.getRepositoryManager().delete('maven-public');
repository.getRepositoryManager().delete('nuget-group');
repository.getRepositoryManager().delete('nuget-hosted');
repository.getRepositoryManager().delete('nuget.org-proxy');
log.info('Script cleanupRepositories completed successfully')
@Grab('org.sonatype.nexus:nexus-rest-client:3.9.0-01')
@Grab('org.sonatype.nexus:nexus-rest-jackson2:3.9.0-01')
@Grab('org.sonatype.nexus:nexus-script:3.9.0-01')
@Grab('org.jboss.spec.javax.servlet:jboss-servlet-api_3.1_spec:1.0.0.Final')
@Grab('com.fasterxml.jackson.core:jackson-core:2.8.6')
@Grab('com.fasterxml.jackson.core:jackson-databind:2.8.6')
@Grab('com.fasterxml.jackson.core:jackson-annotations:2.8.6')
@Grab('com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:2.8.6')
@Grab('org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.0_spec:1.0.1.Beta1')
@Grab('org.jboss.spec.javax.annotation:jboss-annotations-api_1.2_spec:1.0.0.Final')
@Grab('javax.activation:activation:1.1.1')
@Grab('net.jcip:jcip-annotations:1.0')
@Grab('org.jboss.logging:jboss-logging-annotations:2.0.1.Final')
@Grab('org.jboss.logging:jboss-logging-processor:2.0.1.Final')
@Grab('com.sun.xml.bind:jaxb-impl:2.2.7')
@Grab('com.sun.mail:javax.mail:1.5.6')
@Grab('org.apache.james:apache-mime4j:0.6')
@GrabExclude('org.codehaus.groovy:groovy-all')
import javax.ws.rs.NotFoundException
import org.sonatype.nexus.script.ScriptClient
import org.sonatype.nexus.script.ScriptXO
import org.jboss.resteasy.client.jaxrs.BasicAuthentication
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder
import groovy.json.JsonOutput
#!/bin/bash
groovy -Dgroovy.grape.report.downloads=true dependencies.groovy
printf "\nDependencies Loaded\n\n"
version: '3.8'
services:
nexus-init:
image: seed/sonatype/nexus3-init:1.0.0
build: .
FROM seed/ubuntu/focal:1.0.0
RUN apt-get update \
&& apt-get install -y dos2unix netcat groovy curl \
&& apt-get clean
# Preload dependenices for faster init execution
# this will take a while, trust me, you think its hanging, but then no
COPY dependencies.groovy dependencies.groovy
COPY dependencies.sh dependencies.sh
RUN chmod +x dependencies.sh \
&& dos2unix dependencies.sh \
&& ./dependencies.sh
# copy our groovy
COPY addUpdateScript.groovy /init/addUpdateScript.groovy
COPY security.groovy /init/security.groovy
COPY rawRepositories.groovy /init/rawRepositories.groovy
COPY cleanupRepositories.groovy /init/cleanupRepositories.groovy
COPY dockerRepositories.groovy /init/dockerRepositories.groovy
COPY mavenRepositories.groovy /init/mavenRepositories.groovy
COPY aptRepositories.groovy /init/aptRepositories.groovy
COPY buildArchiveRepositories.groovy /init/buildArchiveRepositories.groovy
# wait for so we can do ze wait
ARG WAITFOR_VERSION=2.2.1
ARG WAITFOR_CHECKSUM=e38cb3073a844983aee8780a34959a293dad27006f95ec4e249ef81ded057ca1
RUN curl -fsSL https://github.com/eficode/wait-for/releases/download/v${WAITFOR_VERSION}/wait-for -o /wait-for \
&& sha256sum /wait-for \
&& echo "${WAITFOR_CHECKSUM} /wait-for" | sha256sum -c - \
&& chmod +x /wait-for
#-------------
# actual init now
COPY init.sh /init/init.sh
RUN chmod +x /init/init.sh \
&& dos2unix /init/init.sh
COPY entry.sh /entry.sh
RUN chmod +x /entry.sh \
&& dos2unix /entry.sh
CMD ["./entry.sh"]
import org.sonatype.nexus.blobstore.api.BlobStoreManager
import org.sonatype.nexus.repository.config.WritePolicy
def blobStoreDockerHosted = blobStore.createFileBlobStore('docker-hosted', 'docker-hosted')
repository.createDockerHosted('docker-internal', 8083, null, blobStoreDockerHosted.name, true, true, WritePolicy.ALLOW_ONCE)
log.info('Created Hosted')
def blobStoreProxy = blobStore.createFileBlobStore('docker-proxy', 'docker-proxy')
repository.createDockerProxy('docker-hub', // name
'https://registry-1.docker.io', // remoteUrl
'HUB', // indexType
null, // indexUrl
null, // httpPort
null, // httpsPort
blobStoreProxy.name, // blobStoreName
true, // strictContentTypeValidation
true // v1Enabled
)
log.info('Created Proxy Docker Hub')
// create group and allow access via https
def groupMembers = ['docker-hub', 'docker-internal']
repository.createDockerGroup('docker-all', 8082, null, groupMembers, true)
log.info('Created Combined')
log.info('Script dockerRepositories completed successfully')
#!/bin/bash
# A simple example script that publishes a number of scripts to the Nexus Repository Manager
# and executes them.
# fail if anything errors
set -e
# fail if a function call is missing an argument
set -u
username=admin
password=admin123
# add the context if you are not using the root context
host=http://${nexushost}:8081
# add a script to the repository manager and run it
function addAndRunScript {
name=$1
file=$2
groovy -Dgroovy.grape.report.downloads=false /init/addUpdateScript.groovy -u "$username" -p "$password" -n "$name" -f "$file" -h "$host"
printf "\nPublished $file as $name\n\n"
curl -v -X POST -u $username:$password --header "Content-Type: text/plain" "$host/service/rest/v1/script/$name/run"
printf "\nSuccessfully executed $name script\n\n\n"
}
printf "Provisioning Integration API Scripts Starting \n\n"
printf "Publishing and executing on $host\n"
addAndRunScript security /init/security.groovy
addAndRunScript cleanup /init/cleanupRepositories.groovy
addAndRunScript raw /init/rawRepositories.groovy
addAndRunScript docker /init/dockerRepositories.groovy
addAndRunScript maven /init/mavenRepositories.groovy
addAndRunScript maven /init/aptRepositories.groovy
addAndRunScript maven /init/buildArchiveRepositories.groovy
printf "\nProvisioning Scripts Completed\n\n"
import org.sonatype.nexus.blobstore.api.BlobStoreManager
import org.sonatype.nexus.repository.config.WritePolicy
import org.sonatype.nexus.repository.maven.VersionPolicy
import org.sonatype.nexus.repository.maven.LayoutPolicy
// maven hosted
def blobStoreMavenHosted = blobStore.createFileBlobStore('maven-hosted', 'maven-hosted')
repository.createMavenHosted('maven-releases', blobStoreMavenHosted.name, true, VersionPolicy.RELEASE, WritePolicy.ALLOW_ONCE, LayoutPolicy.STRICT)
/**
* Create a Maven proxy repository.
* @param name The name of the new Repository
* @param remoteUrl The url of the external proxy for this Repository
* @param blobStoreName The BlobStore the Repository should use
* @param strictContentTypeValidation Whether or not the Repository should enforce strict content types
* @param versionPolicy The {@link VersionPolicy} for the Repository
* @param layoutPolicy The {@link LayoutPolicy} for the Repository
* @return the newly created Repository
*/
/*
Repository createMavenProxy(final String name,
final String remoteUrl,
final String blobStoreName,
final boolean strictContentTypeValidation,
final VersionPolicy versionPolicy,
final LayoutPolicy layoutPolicy);
*/
// maven proxy
def blobStoreMavenProxy = blobStore.createFileBlobStore('maven-proxy', 'maven-proxy')
repository.createMavenProxy('maven-central',
'https://repo1.maven.org/maven2/',
blobStoreMavenProxy.name,
false,
VersionPolicy.RELEASE,
LayoutPolicy.PERMISSIVE)
repository.createMavenProxy('maven-clojars',
'https://repo.clojars.org/',
blobStoreMavenProxy.name,
false,
VersionPolicy.RELEASE,
LayoutPolicy.PERMISSIVE)
// now maven group
def groupMembers = ['maven-releases', 'maven-central', 'maven-clojars']
repository.createMavenGroup('maven-public', groupMembers, 'default');
import org.sonatype.nexus.repository.config.WritePolicy
// create a new blob store dedicated to usage with raw repositories
def blobStore = blobStore.createFileBlobStore('raw', 'raw')
// for any files, only allow once
repository.createRawHosted('dml', blobStore.name, false, WritePolicy.ALLOW_ONCE)
// for any apt keys
repository.createRawHosted('apt-keys', blobStore.name, false, WritePolicy.ALLOW_ONCE)
log.info('Script rawRepositories completed successfully')
//
// disable anonymous access
//
security.setAnonymousAccess(false)
log.info('Anonymous access disabled')
The Nexus3 initialization container is responsible for all configuration aspects now that scripting has been turned on.
- Disable Anonymous Access, aka you must login to see anything (security.groovy)
- Remove All preconfigured Repository information (cleanupRepositories.groovy)
- Create the raw repository used for our DML (rawRepositories.groovy)
- Create all Docker Proxies and Private Registries (dockerRepositories.groovy)
- Create Maven Proxy and Private Repositories (mavenRepositories.groovy)
Steps #4 and #5 include typical examples of added third party Docker Registries or Maven Repos. While we are leveraging the RepositoryAPI in our setup scripts, not all features we need are available; one of them being able to update existing Maven/Docker Groups. To overcome this we simply remove anything that is preconfigured via our cleanupRepositories.groovy and re-add it via API.
For more information on the API and additional scripts such as users etc refer to the following:
- Repository API - https://github.com/sonatype/nexus-public/blob/master/plugins/nexus-script-plugin/src/main/java/org/sonatype/nexus/script/plugin/RepositoryApi.java
- Scripting Examples - https://github.com/sonatype-nexus-community/nexus-scripting-examples
As we are leveraging the same ubuntu and JDK image we previously build during our default install, all we need now are builds for the updated nexus3 and nexus3 init.
docker-compose -f seed/nexus-init/docker-compose.yml build
Blue Box of Information: Open Source Changes outside of your control
As we are re-installing our entire infrastructure quite frequently, updating to newer versions to ensure we use the latest LTS, we tend to run into slight hiccups every once in a while. It is important to note that we prefer smaller hiccups more frequently, than waiting a long time frame between re-builds and potentially having a significant effort ahead of us that maybe difficult to estimate and complete. To showcase minor changes that required rework on Nexus3 automation.
As of Oct 2020, three changes in a time frame of 15 months, with each change breaking our existing code on Version upgrades.
Nexus Repository Manager 3.28.0 - 2020 Oct 1st
Refactor of References to automatically create repositories from groovy scripts. Nexus had refactored some class references.
org.sonatype.nexus.repository.storage.WritePolicy
Turned into
org.sonatype.nexus.repository.config.WritePolicy
This little change will likely be the reason for many failures of automated scripts you maybe running when using
org.sonatype.nexus.script.plugin.RepositoryApi
The change was easy to adjust and only had minor impact, but remember, we are working with third party software, that can change at any point in time. It is not uncommon for Open Source Initiatives to go through major refactors in a short time frame and potentially even breaking backwards compatibility. Just as you do with your own code, iterative often, iterate small.
Nexus Repository Manager 3.21.2 - 2020 Mar 23
Disabled Groovy scripts by default. Nice for added security for "default" installs, but needed changes for automation.
Repository Manager 3.17.0 - 2019 Jun 23
Disabled default admin password. Great improvement for security on default installs, but prevented automated and scripted installs as we could no longer rely on the same "initial password" unless we had access to the password file that was created on first startup.
Blue Box of Information: Runtime vs Buildtime
You may see the file dependencies.groovy that appears empty besides imports. This is executed as part of the docker build and ensures that all groovy dependencies for script execution are downloaded at build time. The other groovy files only get executed when the container is running via the init.sh. Without pre-downloading them first, we will have to wait to download the dependency at runtime. So we are shifting the download of dependencies to build time instead to ensure that if any of them fail, they do so at build time and preventing errors to happen while the container is running.
Blue Box of Information: wait-for
We are making use of the tool called "wait-for" (https://github.com/mrako/wait-for) which allows us to easily create chained docker executions. Wait-for is quite popular and is best used in scenarios where a docker container may require time before the process it isolates becomes available. In this case, we want to wait for the nexus3 application to become available first, so we can then properly execute scripts to configure. wait-for does require netcat to be installed so be sure to install it on your distro.
Populating the DML
Leveraging the same side car principle as during the initial configuration, we will be uploading all required files to re-create the steps executed thus far. Those are:
- Ubuntu Docker Image artifacts
- OpenJDK8 Binaries
- Nexus3 Binaries
- Wait-for Script
SERIAL=20220801
40795f39d0f4d3187c0132c5c1d0cb73195caaa8097e0486d75f8edc8cd109b0 ubuntu-focal-oci-amd64-root.manifest
908896fee17c7c3c7b35981d3e5030afa066f2a757a00df9521390308e15275c ubuntu-focal-oci-amd64-root.tar.gz
afde2e714e09e78ca4bbeb2700ac706cd7b174fab0b5525edc5fec0458c0bfab ubuntu-focal-oci-arm64-root.manifest
2099d1899e7e6b110a2bb269edb7e2322e4f75a266f6e562ae3c1c189e06394d ubuntu-focal-oci-arm64-root.tar.gz
45bfb3d20ad9797fd3442e4179ef4bc28a7681c77bd3c74b8e2484a8afd67e3b ubuntu-focal-oci-armhf-root.manifest
1cdd75754be4bbb65155133d91d0521cf206839ce5ad41fa0cef1b0df74ad9ac ubuntu-focal-oci-armhf-root.tar.gz
979c58ab736eaf720ae1244e396590a271ddac602208b28223de53c06efeca81 ubuntu-focal-oci-ppc64el-root.manifest
d3f7688ce2d1bb682783f6b10d37ca0d77e1115384932f29d9bf8b98b973c4ef ubuntu-focal-oci-ppc64el-root.tar.gz
e5b5dfc3ffe3168e28b965a64f5c8643d619f612268dabd9cd17029f1feeab2d ubuntu-focal-oci-riscv64-root.manifest
126291630fbacebacfec221e0d3f12653f4857ad989257dc17fe78300dd5d7b0 ubuntu-focal-oci-riscv64-root.tar.gz
d9a475e2eddc1bec1fc75a257095aeced3156f17c0d0a1c0a72309bfe70575ae ubuntu-focal-oci-s390x-root.manifest
c834e3f65025b7cb79e0f5749ba496fda8e5233ce5a37641ddc0ff7a832a62a9 ubuntu-focal-oci-s390x-root.tar.gz
n/a
adduser 3.118ubuntu2
apt 2.0.9
base-files 11ubuntu5.5
base-passwd 3.5.47
bash 5.0-6ubuntu1.2
bsdutils 1:2.34-0.1ubuntu9.3
bzip2 1.0.8-2
coreutils 8.30-3ubuntu2
dash 0.5.10.2-6
debconf 1.5.73
debianutils 4.9.1
diffutils 1:3.7-3
dpkg 1.19.7ubuntu3.2
e2fsprogs 1.45.5-2ubuntu1.1
fdisk 2.34-0.1ubuntu9.3
findutils 4.7.0-1ubuntu1
gcc-10-base:amd64 10.3.0-1ubuntu1~20.04
gpgv 2.2.19-3ubuntu2.2
grep 3.4-1
gzip 1.10-0ubuntu4.1
hostname 3.23
init-system-helpers 1.57
libacl1:amd64 2.2.53-6
libapt-pkg6.0:amd64 2.0.9
libattr1:amd64 1:2.4.48-5
libaudit-common 1:2.8.5-2ubuntu6
libaudit1:amd64 1:2.8.5-2ubuntu6
libblkid1:amd64 2.34-0.1ubuntu9.3
libbz2-1.0:amd64 1.0.8-2
libc-bin 2.31-0ubuntu9.9
libc6:amd64 2.31-0ubuntu9.9
libcap-ng0:amd64 0.7.9-2.1build1
libcom-err2:amd64 1.45.5-2ubuntu1.1
libcrypt1:amd64 1:4.4.10-10ubuntu4
libdb5.3:amd64 5.3.28+dfsg1-0.6ubuntu2
libdebconfclient0:amd64 0.251ubuntu1
libext2fs2:amd64 1.45.5-2ubuntu1.1
libfdisk1:amd64 2.34-0.1ubuntu9.3
libffi7:amd64 3.3-4
libgcc-s1:amd64 10.3.0-1ubuntu1~20.04
libgcrypt20:amd64 1.8.5-5ubuntu1.1
libgmp10:amd64 2:6.2.0+dfsg-4
libgnutls30:amd64 3.6.13-2ubuntu1.6
libgpg-error0:amd64 1.37-1
libhogweed5:amd64 3.5.1+really3.5.1-2ubuntu0.2
libidn2-0:amd64 2.2.0-2
liblz4-1:amd64 1.9.2-2ubuntu0.20.04.1
liblzma5:amd64 5.2.4-1ubuntu1.1
libmount1:amd64 2.34-0.1ubuntu9.3
libncurses6:amd64 6.2-0ubuntu2
libncursesw6:amd64 6.2-0ubuntu2
libnettle7:amd64 3.5.1+really3.5.1-2ubuntu0.2
libp11-kit0:amd64 0.23.20-1ubuntu0.1
libpam-modules:amd64 1.3.1-5ubuntu4.3
libpam-modules-bin 1.3.1-5ubuntu4.3
libpam-runtime 1.3.1-5ubuntu4.3
libpam0g:amd64 1.3.1-5ubuntu4.3
libpcre2-8-0:amd64 10.34-7
libpcre3:amd64 2:8.39-12ubuntu0.1
libprocps8:amd64 2:3.3.16-1ubuntu2.3
libseccomp2:amd64 2.5.1-1ubuntu1~20.04.2
libselinux1:amd64 3.0-1build2
libsemanage-common 3.0-1build2
libsemanage1:amd64 3.0-1build2
libsepol1:amd64 3.0-1ubuntu0.1
libsmartcols1:amd64 2.34-0.1ubuntu9.3
libss2:amd64 1.45.5-2ubuntu1.1
libstdc++6:amd64 10.3.0-1ubuntu1~20.04
libsystemd0:amd64 245.4-4ubuntu3.17
libtasn1-6:amd64 4.16.0-2
libtinfo6:amd64 6.2-0ubuntu2
libudev1:amd64 245.4-4ubuntu3.17
libunistring2:amd64 0.9.10-2
libuuid1:amd64 2.34-0.1ubuntu9.3
libzstd1:amd64 1.4.4+dfsg-3ubuntu0.1
login 1:4.8.1-1ubuntu5.20.04.2
logsave 1.45.5-2ubuntu1.1
lsb-base 11.1.0ubuntu2
mawk 1.3.4.20200120-2
mount 2.34-0.1ubuntu9.3
ncurses-base 6.2-0ubuntu2
ncurses-bin 6.2-0ubuntu2
passwd 1:4.8.1-1ubuntu5.20.04.2
perl-base 5.30.0-9ubuntu0.2
procps 2:3.3.16-1ubuntu2.3
sed 4.7-1
sensible-utils 0.0.12+nmu1
sysvinit-utils 2.96-2.1ubuntu1
tar 1.30+dfsg-7ubuntu0.20.04.2
ubuntu-keyring 2020.02.11.4
util-linux 2.34-0.1ubuntu9.3
zlib1g:amd64 1:1.2.11.dfsg-2ubuntu1.3
version: '3.3'
services:
nexus-dml:
image: seed/dml:1.0.0
build: .
FROM seed/ubuntu/focal:1.0.0
RUN apt-get update \
&& apt-get install -y curl netcat dos2unix \
&& apt-get clean
#####################################
## UBUNTU FOCAL 20.04
## https://github.com/tianon/docker-brew-ubuntu-core/tree/dist-amd64/focal
#####################################
COPY ubuntu-focal/build-info.txt /tmp/ubuntu-focal/build-info.txt
COPY ubuntu-focal/SHA256SUMS /tmp/ubuntu-focal/SHA256SUMS
COPY ubuntu-focal/ubuntu-focal-oci-amd64-root.tar.gz /tmp/ubuntu-focal/ubuntu-focal-oci-amd64-root.tar.gz
COPY ubuntu-focal/ubuntu-focal-oci-amd64.manifest /tmp/ubuntu-focal/ubuntu-focal-oci-amd64.manifest
#####################################
## OpenJDK 8
## https://github.com/AdoptOpenJDK/openjdk-docker/tree/master/8/jdk/ubuntu
## Dockerfile.hotspot.releases.full
#####################################
ENV JDK8_ESUM=0949505fcf42a1765558048451bb2a22e84b3635b1a31dd6191780eeccaa4ada
ENV JDK8_VERSION=jdk8u292-b10
ENV JDK8_FILE=OpenJDK8U-jdk_x64_linux_hotspot_8u292b10.tar.gz
RUN curl -fsSL https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/${JDK8_VERSION}/${JDK8_FILE} -o /tmp/${JDK8_FILE} \
#&& sha256sum /tmp/${JDK8_FILE} \
&& echo "${JDK8_ESUM} /tmp/${JDK8_FILE}" | sha256sum -c -
#####################################
## NEXUS
## https://help.sonatype.com/repomanager3/release-notes
## https://help.sonatype.com/repomanager3/download/download-archives---repository-manager-3
#####################################
ARG NEXUS_VERSION=3.41.0-01
ARG NEXUS_CHECKSUM=ce265e627a665f9e833bf9c6e15a58c739882eb753863062f9e42e83e6f0d844
RUN curl -fsSL https://download.sonatype.com/nexus/3/nexus-${NEXUS_VERSION}-unix.tar.gz -o /tmp/nexus-${NEXUS_VERSION}-unix.tar.gz \
&& sha256sum /tmp/nexus-${NEXUS_VERSION}-unix.tar.gz \
&& echo "${NEXUS_CHECKSUM} /tmp/nexus-${NEXUS_VERSION}-unix.tar.gz" | sha256sum -c -
#####################################
## WAIT-FOR
## https://github.com/eficode/wait-for
#####################################
ARG WAITFOR_VERSION=2.2.1
ARG WAITFOR_CHECKSUM=e38cb3073a844983aee8780a34959a293dad27006f95ec4e249ef81ded057ca1
RUN curl -fsSL https://github.com/eficode/wait-for/releases/download/v${WAITFOR_VERSION}/wait-for -o /tmp/wait-for \
#&& sha256sum /tmp/wait-for \
&& echo "${WAITFOR_CHECKSUM} /tmp/wait-for" | sha256sum -c -
#-------------
# actual init now
COPY init.sh /init/init.sh
RUN chmod +x /init/init.sh \
&& dos2unix /init/init.sh
# we need the wait for in here actually
RUN cp /tmp/wait-for /wait-for \
&& chmod +x /wait-for
COPY entry.sh /entry.sh
RUN chmod +x /entry.sh \
&& dos2unix /entry.sh
CMD ["./entry.sh"]
#!/bin/bash
uploadIfNotExists() {
echo $1
echo $2
echo $3
# $1 src
# $2 destination
# $3 mame
if curl --output /dev/null --silent --fail -r 0-0 "${NEXUS_BASE_URL}$2"; then
echo "$3 exists skipping."
else
curl -v --user 'admin:admin123' --upload-file $1 ${NEXUS_BASE_URL}$2
fi
}
printf "Starting"
# fail if anything errors
set -e
NEXUS_BASE_URL=http://${nexushost}:8081/repository/dml/docker
#########################################
BUILD_INFO=20211006
uploadIfNotExists /tmp/ubuntu-focal/build-info.txt /ubuntu/focal/${BUILD_INFO}/build-info.txt "ubuntu-buildinfo"
uploadIfNotExists /tmp/ubuntu-focal/SHA256SUMS /ubuntu/focal/${BUILD_INFO}/SHA256SUMS "ubuntu-shasums"
uploadIfNotExists /tmp/ubuntu-focal/ubuntu-focal-oci-amd64-root.tar.gz /ubuntu/focal/${BUILD_INFO}/ubuntu-focal-oci-amd64-root.tar.gz "ubuntu-tar"
uploadIfNotExists /tmp/ubuntu-focal/ubuntu-focal-oci-amd64.manifest /ubuntu/focal/${BUILD_INFO}/ubuntu-focal-oci-amd64.manifest "ubuntu-manifest"
JDK8_VERSION=jdk8u292-b10
JDK8_FILE=OpenJDK8U-jdk_x64_linux_hotspot_8u292b10.tar.gz
uploadIfNotExists /tmp/${JDK8_FILE} /jdk/${JDK8_VERSION}/${JDK8_FILE} "jdk8"
NEXUS_VERSION=3.41.0-01
uploadIfNotExists /tmp/nexus-${NEXUS_VERSION}-unix.tar.gz /nexus/nexus-${NEXUS_VERSION}-unix.tar.gz "nexus3"
uploadIfNotExists /tmp/wait-for /tools/wait-for "wait-for"
#########################################
Note: the only files being added directly from local disk are the ubuntu files we previously downloaded for our initial base image (see ubuntu subfolder). All other files such as JDK, Nexus3 are downloaded during the build process.
For application containers it is generally a good practice to "download && unpackage && remove" as chained commands to keep the image layers slim. However, as we are simply isolating a one-time process for the DML, we opt to not only download the binaries, but also keep them on the container itself, ensuring that during build time all files could be downloaded and verified for integrity. End result, at runtime you are guaranteed to have access to your files and simply upload. Finally, you can now access those files either inside the Docker Image, on the DML once uploaded, or on the original download location.
Depending on the number of third party applications and resources you are going to manage, you will end up in a situation where an "original download" location has moved or is no longer accessible at one point, having easy access to the file as needed without having to worry about finding the new download location is always going to be helpful.
Again, for now just build it.
docker-compose -f seed/nexus-dml/docker-compose.yml build
Networking & Hostnames
While we only rely on docker-compose, the exception is the creation of the initial network that all our containers will be running in. This is done via a plain-old docker command.
docker network create --driver=bridge --subnet=172.22.90.0/16 --gateway=172.22.0.1 ops-network
As we work with the network, we also will be creating hostnames for each of our containers that others will used for network communication. Hostnames follow the convention of [environment][environment-number][internal/external]-[host-type]-[descriptor]
Consider the following examples:
- d1i-win-sql01 - Development Environment #1 Internal (aka not publically reachable) Windows SQL Server 01
- s2i-doc-app01 - Staging Environment #2 Internal Docker Apps 01
Running the Automated Seed Nexus3 install
We simply start our nexus3 image and attach our two setup containers, the initial config and then the DML container to populate third party files.
version: '3.3'
services:
nexus:
image: seed/sonatype/nexus3:1.0.0
hostname: d1i-doc-nexus01
volumes:
- nexus-data:/nexus-data
ports:
- 8081:8081
networks:
- ops-network
volumes:
nexus-data:
name: nexus-data
networks:
ops-network:
name: ops-network
As we want to run this as a background process we simply create the resources and the start it.
docker-compose -f composed/docker-compose-seed.yml up --no-start
docker-compose -f composed/docker-compose-seed.yml start
You can optionally check the logs via the below command to confirm Nexus3 has started.
docker-compose -f composed/docker-compose-seed.yml logs -f --tail="50"
The following message will appear: Started Sonatype Nexus OSS [version]. Once started, you can access the instance at http://localhost:8081 with username admin and password admin123.
Next step is starting the configuration container using the following compose compose file.
version: '3.8'
services:
nexus-init:
image: seed/sonatype/nexus3-init:1.0.0
hostname: d1i-doc-nxsinit
environment:
nexushost: d1i-doc-nexus01
networks:
- ops-network
networks:
ops-network:
name: ops-network
Run it via:
docker-compose -f composed/docker-compose-seed-init.yml up
The nexus-init container will start executing scripts once the HTTP port is available at http://localhost:8081. Once the initialization is completed the init process will terminate.
Important Note: Keep in mind this process is meant to only be run once as scripts assume to be executing against an untouched freshly started instance. If you want to re-do the initialization, remove/delete volume or mount and re-run it again.
Same with the DML via the following compose file.
version: '3.3'
services:
nexus-dml:
image: seed/dml:1.0.0
hostname: d1i-doc-nxsdml
environment:
nexushost: d1i-doc-nexus01
networks:
- ops-network
networks:
ops-network:
name: ops-network
Run it to start the upload.
docker-compose -f composed/docker-compose-seed-dml.yml up
Same as before, the upload will start once the HTTP port is available at http://localhost:8081. Once the upload is complete, the dml process will terminate.
Summary
The initial part of our seed installation is completed and we now have a Nexus3 Volume fully populated. As all prior steps are meant to be run ONE time, we now want to run the fully installed Nexus3 instance connected to our final volume via the following compose file.

